MVC приложение на php. Часть 6. crud, mapping

В этой статье сделаем админку полезной — она сможет создавать статьи, категории, редактировать, удалять. А также привяжем статьи к категориям.
Плодить контроллеры не будем, все добавим в 2 базовых.
Итак, форма для ред-ия та же что для просмотра, соот-но экшн будет одинаковый. Принимаем post данные

application/controllers/admin/articles.php


    /**
     * 
     */
    public function wrap_images(&$item, $key){
        $item = "<img src='images/$item' alt='' class='preview'>";
    }

    /**
     * @param array $args
     */
    function article(array $args = null){
        $model = new Articles1();

        if(!empty($_POST)){

            /* base values */
            $data = [];
            foreach ($_POST as $key => $value) {
                global $data;
                if(!empty($value)){
                    $data[$key] = $value;
                }
            }
            /* image  */
            if(!empty($_FILES['image']['name'][0])){
                $data["preview_pic"] = $_FILES['image']['name'][0];
                $uploaded_files = [];
                foreach($_FILES['image']['error'] as $key=> $error){
                    global $uploaded_files;
                    if($error == UPLOAD_ERR_OK){
                        $tmp_name = $_FILES['image']['tmp_name'][$key];
                        $name = basename($_FILES['image']['name'][$key]);
                        $res = move_uploaded_file($tmp_name, PATH_SITE.DS.'images'.DS.$name); 
                        if($res){
                            $uploaded_files[] = $_FILES['image']['name'][$key];
                        }
                    }
                }
                // массив картинок переводим в строку
                $data["image"] = implode(',' , $uploaded_files);
            }

            $res = $model->edit($data);
            echo $res;
        }

        $id = (isset($args[0])? $args[0]: "");
        $article = $model->getById($id);

        // строку преобразуем в массив и оборачиваем тегами
        if(!empty($article['image'])){
            $arr = explode(',' , $article['image']);
            array_walk($arr, [$this , 'wrap_images']);
            $article['image'] = implode('
' ,$arr);
        }
        $data = array(
            'article' => $article,
        );
        $this->view->generate('',  $data);
    }

И метод для записи в БД

application/models/admin/articles.php


    /**
     * 
     */
    public function edit($data = null){
        if(!empty($data)){
            $sql_articles = 'UPDATE article SET';
            $where = 'WHERE id='.$data['id'];
            $result = DateBase::build_query($sql_articles, $data, $where);
            return $result;
        }
    }

Во вьюхе неболшие правки.



            <form action="/admin/articles/article/<?= $article['id'] ?>" method="post" name="form1" enctype="multipart/form-data">
                <input type="hidden" name="id" value="<?= $article['id'] ?>">

Для клиентcкой части добавим в контроллере преобразование картинок в теги, и заменим в представлении вывод.

application/controllers/client/articles.php


    /**
     * 
     */
    public function wrap_images(&$item, $key){
        $item = "<img src='images/$item' alt='' class='preview'>";
    }
        // строку преобразуем в массив и оборачиваем тегами
        if(!empty($article['image'])){
            $arr = explode(',' , $article['image']);
            array_walk($arr, [$this , 'wrap_images']);
            $article['image'] = implode('' ,$arr);
        }
// view
<?=$article['image']?>

Поле категорий пока пустое.
Добавлю экшн для добавления статьи. Он практически полностью копирует функционал редактирования.


    /**
     * 
     */
    public function add(){
         $model = new Articles1();

        if(!empty($_POST)){

            /* base values */
            $data = [];
            foreach ($_POST as $key => $value) {
                global $data;
                if(!empty($value)){
                    $data[$key] = $value;
                }
            }
            /* image  */
            if(!empty($_FILES['image']['name'][0])){
                $data["preview_pic"] = $_FILES['image']['name'][0];
                $uploaded_files = [];
                foreach($_FILES['image']['error'] as $key=> $error){
                    global $uploaded_files;
                    if($error == UPLOAD_ERR_OK){
                        $tmp_name = $_FILES['image']['tmp_name'][$key];
                        $name = basename($_FILES['image']['name'][$key]);
                        $res = move_uploaded_file($tmp_name, PATH_SITE.DS.'images'.DS.$name); 
                        if($res){
                            $uploaded_files[] = $_FILES['image']['name'][$key];
                        }
                    }
                }
                // массив картинок переводим в строку
                $data["image"] = implode(',' , $uploaded_files);
            }

            $res = $model->add($data);
        }

        $data = array();
        $this->view->generate('',  $data);   
    }

Модель для добавления статьи


    /**
     * 
     */
    public function add($data = null){
        if(!empty($data)){
            $sql_articles = 'INSERT INTO article SET';
            $result = DateBase::build_query($sql_articles, $data);
            return $result;
        }
    }

Вьюха тоже как в редактировании.

application/views/admin/addArticle.php


<!-- Left col -->
<section class="col-lg-10 connectedSortable">
    <!-- quick email widget -->
    <div class="box box-info">
        <div class="box-header">
            <h3 class="box-title">Добавить статью</h3>
            <!-- tools box -->
            <div class="pull-right box-tools">
                <button class="btn btn-info btn-sm" data-widget="remove" data-toggle="tooltip" title="Remove"><i
                        class="fa fa-times"></i></button>
            </div>
            <!-- /. tools -->
        </div>
        <div class="box-body">
            <!--       ////////////////////////////////  form     //////////////////////////////       -->
            <form action="/admin/articles/add" method="post" name="add_article" enctype="multipart/form-data">
                <div class="form-group">
                    <input type="text" class="form-control" name="title" placeholder="Название статьи"/>
                </div>
                <!--          datepicker          -->
                <div class="form-group">
                    <label>Дата:</label>

                    <div class="input-group">
                        <div class="input-group-addon">
                            <i class="fa fa-calendar"></i>
                        </div>
                        <input type="text" class="form-control pull-right" id="datepicker" name="date">
                    </div>
                    <!-- /.input group -->
                </div>
                <!--         anons           -->
                <div class="form-group">
                    <label for="anons">Анонс</label><br/>
                    <textarea name="anons" id="anons"></textarea>
                </div>
                <!--          text description          -->
                <div class='box'>
                    <div class='box-header'>
                        <h3 class='box-title'>Текст статьи</h3>
                        <!-- tools box -->
                        <div class="pull-right box-tools">
                            <button class="btn btn-default btn-sm" data-widget='collapse' data-toggle="tooltip"
                                    title="Collapse"><i class="fa fa-minus"></i></button>
                            <button class="btn btn-default btn-sm" data-widget='remove' data-toggle="tooltip"
                                    title="Remove"><i class="fa fa-times"></i></button>
                        </div>
                        <!-- /. tools -->
                    </div>
                    <!-- /.box-header -->
                    <div class='box-body pad'>
                        <div>
                            <textarea name="description" class="textarea" id="editor1"
                                      placeholder="Place some text here"
                                      style="width: 100%; height: 200px; font-size: 14px; line-height: 18px; border: 1px solid #dddddd; padding: 10px;"></textarea>
                        </div>
                    </div>
                </div>
                <!-- Select multiple-->
                <div class="form-group">
                    <label>Выберите категорию:</label>
                    <select multiple class="form-control" name="category[]">
                        <? foreach ($data['categories'] as $categories): ?>
                            <option value="<?= $categories['id'] ?>"><?= $categories['name'] ?></option>
                        <? endforeach ?>
                    </select>
                </div>
                <div class="form-group">
                    <label for="exampleInputFile">Загрузка файлов</label>
                    <input name="image[]" type="file" multiple="multiple" id="exampleInputFile">

                    <p class="help-block">Выберите файлы для загрузки</p>
                </div>
                <div class="box-footer clearfix">
                    <button type="submit" class="pull-right btn btn-default" name="send">Добавить <i
                            class="fa fa-arrow-circle-right"></i></button>
                </div>
            </form>
            <!--       ////////////////////////////////  end form   //////////////////////////////       -->
        </div>
    </div>

</section><!-- /.Left col -->

Остался экшн для удаления статьи.


    /**
     * 
     */
    public function delete($args = null){
       $id = (isset($args[0])? $args[0]: ""); 
       if($id){
            $model = new Articles1();
            $article = $model->getById($id);
           $res = $model->delete($id);
            // удаляем картинки
            if($res == true){
                $arr = explode(',' , $article['image']);
                array_walk($arr, [$this, 'delete_images']);
            }
       }
       $this->view->redirect('/admin');
    }

    /**
     * 
     */
    public function delete_images($item){
        if(file_exists(PATH_SITE.DS.'images'.DS.$item)){
            unlink(PATH_SITE.DS.'images'.DS.$item);
        }
    }

Удалим из БД


    /**
     * 
     */
    public function delete($id = null){
        if(!empty($id)){
            $sql = 'DELETE FROM article WHERE id = %s';
            $result = DateBase::query($sql, $id);
        }
    }

Вид — это иконка удаления в таблице


<td>
                            <a href="/admin/articles/delete/<?= $article['id'] ?>" class="delete_item"
                               data-confirm-title="Удалить запись?"
                               data-confirm-message="Вы действительно хотите удалить эту запись?">
                                <span class="glyphicon glyphicon-trash"></span>
                            </a>
                        </td> 

Для подтверждения добавил мод. окно от бутстрап


<script src="js/confirm-bootstrap.js"></script>

// app.js
    $(".delete_item").confirmModal();

Категории. CRUD

Поработаем с категориями. Все практич. тож самое, даже попроще, не будет возни с картинками.

application/controllers/admin/categories.php


        if(!empty($_POST)){
            /* base values */
            $data = [];
            foreach ($_POST as $key => $value) {
                global $data;
                if(!empty($value)){
                    $data[$key] = $value;
                }
            }
            $res = $model->edit($data);
        }

Апдейт в БД

application/models/admin/categories.php


    /**
     * 
     */
    public function edit($data = null){
        if(!empty($data)){
            $sql_articles = 'UPDATE category SET';
            $where = 'WHERE id='.$data['id'];
            $result = DateBase::build_query($sql_articles, $data, $where);
            return $result;
        }
    }

Небольшие правки в представлении


            <form action="/admin/categories/category/<?= $category['id'] ?>" method="post">
                <input type="hidden" name="id" value="<?= $category['id'] ?>">

Добавление категории


    /**
     * 
     */

    public function add(array $args = null){
        $model = new categories1();
        $id = (isset($args[0])? $args[0]: "");

        if(!empty($_POST)){
            /* base values */
            $data = [];
            foreach ($_POST as $key => $value) {
                global $data;
                if(!empty($value)){
                    $data[$key] = $value;
                }
            }
            $res = $model->add($data);
        }

        $data = array();
        $this->view->generate('addCategory', $data);
    }

Model


    /**
     * 
     */
    public function add($data = null){
        if(!empty($data)){
            $sql_articles = 'INSERT INTO category SET';
            $result = DateBase::build_query($sql_articles, $data);
            return $result;
        }
    }

Вид


<section class="col-lg-7 connectedSortable">
    <!-- quick email widget -->
    <div class="box box-info">
        <div class="box-header">
            <h3 class="box-title">Добавить категорию</h3>
            <!-- tools box -->
            <div class="pull-right box-tools">
                <button class="btn btn-info btn-sm" data-widget="remove" data-toggle="tooltip" title="Remove"><i
                        class="fa fa-times"></i></button>
            </div>
            <!-- /. tools -->
        </div>
        <div class="box-body">
            <form action="/admin/categories/add/" method="post">
                <div class="form-group">
                    <input type="text" class="form-control" name="name" placeholder="Название категории" value="" />
                </div>
                <div>
                    <textarea name="description" class="textarea" placeholder="Описание "
                              style="width: 100%; height: 125px; font-size: 14px; line-height: 18px; border: 1px solid #dddddd; padding: 10px;"></textarea>
                </div>
                <div class="box-footer clearfix">
                    <button class="pull-right btn btn-default" type="submit" name="send">Добавить <i
                            class="fa fa-arrow-circle-right"></i></button>
                </div>
            </form>
        </div>
    </div>

</section><!-- /.Left col -->

Удаление категории


    /**
     * 
     */
    public function delete($args = null){
       $id = (isset($args[0])? $args[0]: ""); 
       if($id){
            $model = new categories1();
            $res = $model->delete($id);            
       }
       $this->view->redirect('/admin/categories');
    }

Запрос одинаков с articles кроме названия таблицы


    /**
     * 
     */
    public function delete($id = null){
        if(!empty($id)){
            $sql = 'DELETE FROM category WHERE id = %s';
            $result = DateBase::query($sql, $id);
            return $result;
        }
    }

Вид добавляем иконку с модалкой


                        <td>
                            <a href="/admin/categories/delete/<?= $categories['id'] ?>" class="delete_item"
                               data-confirm-title="Удалить запись?"
                               data-confirm-message="Вы действительно хотите удалить эту запись?">
                                <span class="glyphicon glyphicon-trash"></span>
                            </a>
                        </td>

Mapping

Создадим отношение многие к одному. Для этого создадим отд. таблицу category_article

articles___(20-37-36_06-03-2017)__rand945344.sql


CREATE TABLE `category_article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `category_id` int(11) DEFAULT NULL,
  `article_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `article_id` (`article_id`),
  KEY `category_id` (`category_id`),
  CONSTRAINT `category_article_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
  CONSTRAINT `category_article_ibfk_2` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 AVG_ROW_LENGTH=1170;

При удалении статьи или категории удаление будет каскадно.
Добавим список категорий на стр. редакти-я статьи. Для этого в контроллере нужно будет обращение к модели категорий. Напишем автолоад.

application/core/autoload.php


function __autoload($class_name)
{
	define('APP_PATH', 'application');
    $path = strtolower(str_replace("\\", DS, $class_name));

    if (file_exists(PATH_SITE.DS.APP_PATH.DS.$path . ".php")) {
        include_once(PATH_SITE.DS.APP_PATH.DS.$path . ".php");
  	}else {
        header("HTTP/1.0 404 Not Found");
        echo "К сожалению такой страницы не существует. " .PATH_SITE.DS.APP_PATH.DS.$path . ".php ";
        exit;
    }
}

Выведем селект. Получим массив и преобразуем в теги.


use Models\Admin\Categories;
        $categories_mod = new Categories();
        $categories = $categories_mod->get_categories();
        // категории преобразуем в селект
        if(!empty($categories)){
            array_walk($categories, [$this, 'categories_select']);
            $cats = "<select name='category[$id]' multiple='multiple' class='form-control'>";
            $cats .= implode('', $categories);
            $cats .= "</select>";
        }
    /**
     * 
     */
    public function categories_select(&$item, $key){
        $item = "<option value='{$item['id']}'>{$item['name']}</option>";
    }

Возни с циклами во вьюхе не будет


<?=$categories;?>

Обработаем значения категорий


            // категории
            $category = [];
            if(!empty($_POST['category'])){
                foreach ($_POST['category'] as $key => $value) {
                    global $category;
                    $category[$key]['article_id'] = $id; 
                    $category[$key]['category_id'] = $value; 
                }
            }

            $model->edit($data, $category);

 


    /**
     * 
     */
    public function edit($data = null, $category = null){
        if(!empty($data)){
            $sql_articles = 'UPDATE article SET';
            $where = 'WHERE id='.$data['id'];
            $result = DateBase::build_query($sql_articles, $data, $where);
            // return $result;
        }
        if(!empty($category)){
            foreach($category as $cat){
                $sql_articles = 'INSERT INTO category_article SET';
                $result = DateBase::build_query($sql_articles, $cat);            
            }
        }
    }

Выведем результаты в списке статей. Поменяем запрос к табл. статей.


    /**
     * 
     */
    public function get_articles(){
                
        $sql = 'SELECT a.*, GROUP_CONCAT(DISTINCT c.name )as category 
                FROM article a 
                LEFT JOIN category_article ca 
                ON a.id = ca.article_id
                LEFT JOIN category c 
                ON c.id = ca.category_id
                GROUP BY a.id';


        $result = DateBase::query($sql);
        $row2 = array();
        while ($row = $result->fetch_assoc()) {
            array_push($row2 , $row);
        }
        if(empty($row2))return;
        return $row2;
    }

Вид просто



<th>Категории</th>

<td><?= $article['category'] ?></td>

Осталось связывать категории при добавлении статьи


                        // категории
            $category = [];
            if(!empty($_POST['category'])){
                foreach ($_POST['category'] as $key => $value) {
                    global $category;
                    $category[$key]['category_id'] = $value; 
                }
            }

            $res = $model->add($data, $category);

Как видишь тут id сатьи пока нет, она новая, получаем ее по insert_id


    /**
     * 
     */
    public function add($data = null, $category = null){
        if(!empty($data)){
            $sql_articles = 'INSERT INTO article SET';
            $result = DateBase::build_query($sql_articles, $data);
        }
        $id = DateBase::insert_id();
        if(!empty($category) && isset($id)){
            foreach($category as $key=>$cat){
                $cat['article_id'] = $id; 
                $sql_articles = 'INSERT INTO category_article SET';
                $result = DateBase::build_query($sql_articles, $cat);            
            }
        }

    }

Осталось все это добро в клиентской части вывести. По сути работа на копипаст.
Конечно же, функционал админки не самый полный. При редактировании надо категории перезаписывать, а не просто добавлять. Выбранные категории должны отмечаться в селекте. Картинки надо чтобы удалялись ненужные. Все это сделать не сложно.

Leave a comment

Your email address will not be published.


*