Гораздо легче натянуть шаблон на готовые данные, чем подгонять дизайн на готовый html. О чем и говорится. Нужны готовые алгоритмы и взаимодействующие сущности, с помощью которых форум будет намного проще и быстрее сделать.
Я планирую начать с простой гостевой.На ней проще будет заложить скелет аритектуры,и опробовать методы абстрагирования от БД.
Кстати, API форума - штука полезная. Особенно, если оно является частью форума, (т.е. весь форум внутри работает через это API), а не как отдельная прибамбасина. Тогда можно было бы легко совмещать администрирование портала с администрированием форума.
форум - довольно сложная штука сама по себе. У меня еще не было задач сделать форум, хоть для себя хоть для заказчика. Если с получением сообщений/тем все более-менее ясно, то как быть с записью оных? С модерированием и тд? + потребуются разные сортировки, фильтры. Опять же прочитанные/непрочитанные (новые сообщения). вообще для всех таких апи и их отладки удобно б сделать консоль. Как в пыхе открыть консоль, которая б не закрывалась после каждой команды и сохраняла все загруженные библиотеки во время работы?
Я для таких фишек использую усправление скриптом через mysql или файлы.Тоесть скрипт гоняеться по циклу с set_time_limit(0) проверяя с задержками базу на наличие команд.
Нам не нужна абстракция на данном этапе. Хватит того, что выделить работу с SQL в отдельный класс. Использовать MySQLi как базовый класс для абстракции, слегка его расширив обработкой ошибок - для остальных баз потом легко сделать обёртку.
Я вот тоже как-то писал подобное, сделал условия всякие и т.п., потом плюнул и сделал два метода - select_array и просто query
а я — закончил более-менее. да еще и с Join'ами работает так охуенно: PHP: <?php /** * Результат, выполнения запроса * @author Shock */ interface Db_IResult { /** * Определяет, есть ли следующая строка запроса и переходит на неё, либо возвращает false * Использование: while ($result->row()) { } * @return bool */ function row (); /** * Возвращает определённую строку, или массив, или двумерный массив результата из бд. Примеры: * "field" — получить значение последнего поля с таким названием * "*.field" — возвращает одномерный массив, где ключём будет имя таблицы, а значением — значения поля field в соответствующей таблице * "table.*" — получить ассоциативный массив, где ключ — имя поля в таблице table * "*.*" — получить массив, в котором ключи — имена таблиц, а значение — массивы, в которых ключи — имена полей * "*" — получить массив, в котором ключи — имена полей * ".field" — получить значение группового поля, например "COUNT(*)" * @param string $field * @return string|array */ function get ($field); /** * Возвращает autoincrement ID последней вставленной записи * @return int */ function getId (); /** * Возвращает количество затронутых, или полученных select'ом строк * @return int */ function getNum (); /** * Получить ошибку, или null, если ошибок не было * @return string|null */ function getError (); } ?> и получается что-то типа такого: PHP: <?php $result = Registry::get('Db') ->query() ->select('*') ->from('Posts as Post') ->join('Users as User', 'Post.UserID = User.ID') ->where('Post.TopicID = %d', $this->argv['TopicID']) ->compile()->run(); $posts = array(); while ($result->row()) { $post = new Instance_Post($result->get('Posts.*')); $post->author = new Instance_User($result->get('User.*')); $posts[] = $post; } return $posts; Грубо, конечно, но суть понятна
Вообще, конечно, мне больше был важен именно Db_IResult, а не Db_IQueryBuilder, так как нормальной работы с джоинами в пхп я так и не нашёл))
Заранее прошу прощения за "простыню" кода. попытался написать то о чем тут велось обсуждение на примере гостевой книги, получился вот такой набросок класса. Все обработки на правильность отдаються пока основному коду,тут описан функционал с учетом что все данные верны(экранированы даные для запросов и т.п.) не стал писать оболочку для содания гостевой книги, так как каждый знает что там будет, сейчас интересней всего услышать комментарии о самом классе. [sql] CREATE TABLE `guestbook` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `moment` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record timestamp', `name` VARCHAR( 128 ) DEFAULT 'Anonymous' NOT NULL COMMENT 'Guest name', `email` VARCHAR( 128 ) NOT NULL COMMENT 'Guest e-mail', `text` LONGTEXT NOT NULL COMMENT 'Guest book record', PRIMARY KEY ( `id` ) ) CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT = 'Guestbook records' [/sql] PHP: <?php $mysql_database = 'test'; # База данных (может быть false если выбирать не надо) $mysql_host = 'localhost'; # Хост $mysql_user = 'root'; # Пользователь $mysql_pass = '123'; # Пароль $mysql_charset = 'utf8'; # Кодировка $tableName = 'guestbook'; # Имя таблицы для гостевой книги $postPerPage = 10; include('classes.php'); $Guest = new questBook(); $Guest->setDbParams($mysql_host,$mysql_user,$mysql_pass,$mysql_database,$mysql_charset,$tableName); $Guest->setGuestBookParams($postPerPage); foreach($Guest->getAllPosts() as $post) { echo $post->id.' '.$post->moment.' '.$post->name.' '.$post->email.' '.$post->text.'<br>'; } ?> [/code] [code] <?php // Класс работы с mysql class class_mysql { private $mysql_resource; # Идентификатор ресурса подключения private $errors = array(); function __construct($mysql_host,$mysql_user,$mysql_pass,$mysql_database,$mysql_charset) { // Подключаемся if(!($this->mysql_resource=mysql_connect($mysql_host,$mysql_user,$mysql_pass))){ $this->errors[] = ("<pre>Ошибка подключения к БД!\nТекст ошибки:\n".mysql_error()."в class_mysql::__construct</pre>"); die(("<pre>Ошибка подключения к БД!\nТекст ошибки:\n".mysql_error()."в class_mysql::__construct</pre>")); } // Выбираем БД (если надо) if($mysql_database!==false){ if(!(@mysql_select_db($mysql_database,$this->mysql_resource))) $this->errors[] = ("<pre>Ошибка выбора БД!\nТекст ошибки:\n".mysql_error()."в class_mysql::__construct</pre>"); return false;} // Сообщаем кодировку if(!(@mysql_query('SET CHARACTER SET '.$mysql_charset,$this->mysql_resource))) $this->errors[] = ("<pre>Ошибка выбора кодировки БД!\nТекст ошибки: \n".mysql_error()."\nКодировка:\n".$mysql_charset."в class_mysql::__construct</pre>"); return false; } // Возвращает ассоциативный массив из SQL запроса public function query($mysql_query) { // Выполняем запрос if ($this->mysql_resource == false) {// Проверяем установлена ли связь с БД $this->errors[] = "<pre>Не установлено соедедение с БД в class_mysql::query</pre>"; return false; } ($mysql_result=mysql_query($mysql_query,$this->mysql_resource)); if ($mysql_result == false) { $this->errors[] = "<pre>Ошибка выполнения запроса к БД!\nТекст ошибки:\n".mysql_error()."\nЗапрос:\n".htmlspecialchars($mysql_query)." в class_mysql::query</pre>"; return false; } $mysql_return=array(); # Возвращаемый функцией массив // Если был запрос не на получение данных if($mysql_result==1) // Если запрос вставки - возвращаем id новой записи if (($mysql_return=mysql_insert_id($this->mysql_resource))!=0) return $mysql_return; // Иначе - количество затронутых рядов в результате выполнения запроса else return mysql_affected_rows($this->mysql_resource); // Если выполнен запрос на получение данных else { while($line=mysql_fetch_array($mysql_result,MYSQL_ASSOC)) $mysql_return[]=array_change_key_case($line); # Возвращаем ассоциативным массивом то что получили из запроса mysql_free_result($mysql_result); # Освобождаем Результат Запроса Чтобы Не Жрал Память } return $mysql_return; } //возвращает последнюю ошибку public function getLastError(){ if ($this->errors != null) { return $this->errors[count($this->errors)-1]; } } //Возвращает все ошибки за всемя работы public function getAllErrors(){ if ($this->errors != null) { return $this->errors; } } } /** *Класс для работы с гостевой книгой * */ class questBook{ /** * Constructor */ private $tableName = null; //Имя таблицы для гостевой книги private $mysql_database = null; // База данных (может быть false если выбирать не надо) private $mysql_host = null; // Хост private $mysql_user = null; // Пользователь private $mysql_pass = null; // Пароль private $mysql_charset = null; // Кодировка public $db = null; // База данных private $postPerPage = 10; //Кол-во сообщений на страницу private $posts = null; private $postCounts = null; private $pageCount = null; function __construct(){ } function initDb(){ //Функция для инициализации БД вызываеться перед каждой попыткой сделать запрос в БД if ($this->db === null) { //Если база не проинициализирована - инициализируем $this->db = new class_mysql($this->mysql_host, $this->mysql_user, $this->mysql_pass, $this->mysql_database, $this->mysql_charset); } } function setDbParams($mysql_host,$mysql_user,$mysql_pass, $mysql_database,$mysql_charset,$tableName){//Устанавливаем параметры БД $this->mysql_host = $mysql_host; $this->mysql_user = $mysql_user; $this->mysql_pass = $mysql_pass; $this->mysql_database = $mysql_database; $this->mysql_charset = $mysql_charset; $this->tableName = $tableName; } function setGuestBookParams($postPerPage){ //устанавлием параметры гостевой $this->postPerPage = $postPerPage; } function addPost($name,$email,$text){ //Добавлени поста вбазу $this->initDb(); $result = $this->db->query("INSERT INTO `".$this->tableName."` (`name`,`email`,`text`) VALUES ('".$name."', '".$email."', '".$text."');"); if ($result != false) { $this->posts[$result] = $this->getPostById($result); //берем из БД потомучто не знает поле moment if ($this->postCounts = null) { $this->postCounts++; //вписываем что добавился еще один пост $this->getPageCount(); //пересчитываем кол-во страниц } } return $result; } function getPostById($id){ //возвращает пост по его id if (!isset($this->posts[$id])) { $this->initDb(); $result = $this->db->query('SELELECT * FROM `'.$this->tableName.'` WHERE `id`='.$id); if ($result != false) { $this->posts[$id] = new Post($id,$result['momtnt'],$result['name'], $result['email'],$result['text']); } } } function loadAllPosts(){ //Загружает из базы все посты if ($this->posts == null) { $this->initDb(); $result = $this->db->query("SELECT * FROM `".$this->tableName."` WHERE 1;"); if (is_array($result)) { foreach ($result as $post){ $this->posts[$post['id']] = new Post($post['id'],$post['moment'],$post['name'], $post['email'],$post['text']); } } } } function getAllPosts(){ //возращает все посты в базе if ($this->posts == null) $this->loadAllPosts(); return $this->posts; } function getLastPost(){ //Возвращает последний пост if ($this->posts == null) $this->loadAllPosts(); return $this->posts[count($this->posts)-1]; } function getAllBdErrors(){ //Выводил все ошибки связаные с БД $this->initDb(); return $this->db->getAllErrors(); } function getPostsCount(){ //Кол-во постов в базе $this->initDb(); if ($this->postCounts == null) { $countsArray = $this->db->query('SELECT COUNT(*) as counts FROM `'.$this->tableName.'`'); $this->postCounts= intval($countsArray[0]['counts']); } return $this->postCounts; } function getPageCount(){ //Колво страниц с учетом параматра кол-во костов на страницу и все постов в базе $this->initDb(); $this->getPostsCount(); if ($this->postCounts == 0 ) { return 0; } else { return intval($this->postCounts/$this->postPerPage)+1; } } function getPostsByPage($pageNum){ } } /** * * */ class Post{ /** * Constructor */ public $id; public $moment; public $name; public $email; public $text; function __construct($id,$moment,$name,$email,$text){ $this->id = $id; $this->moment = $moment; $this->name = $name; $this->email = $email; $this->text = $text; } } ?>
пока что — так себе. 1. если уж выкладываешь тут, а не ка каком-то pastebin.ru, то обрамляй в тег PHP: <?php ?> а не Код (Text): <?php ?> . 2. Во вторых — такой апи подразумевает порядок и закономерность, интерфейсы, вылизанный код. А ты не смог и один класс вылизать. Получилось ужасно. неразборчиво и труднорасширяемо. это неправильно. суть апи в том, что я не хочу ебатся с этими всеми деталями. я хочу сделать так: PHP: <?php $guest = new Api_Guest ($guestConfig); $post = $guest->createPost(array( 'author' => Session::get('User')->id, 'text' => Request::get('text') ))->save(); // Или так: $posts = $guest->getPage(Request::getInt('page')); // Или так: $topic = $forum->getTopic(array( 'id' => Request::getInt('topic'), 'page' => Request::getInt('page') )); Я понимаю, что посидеть с ручкой и продумать структуру — впадлу, гараздо интереснее сразу же кинуться в бой и написать крутую АПИ, но пока получилось лажово. Даже на такой примитивной штуке, как гостевая. Потренируйся еще именно в планировании и написании интерфейсов. Напиши интерфейсы, напиши тесты. Именно они интересны нам, конечным пользователям.А как оно там работает внутри — нас не должно интересовать. Абы не тормозило и было безопасно
PHP: <?php /***************/ $this->posts[$id] = new Post($id,$result['momtnt'],$result['name'], $result['email'],$result['text']); /***************/ function __construct($id,$moment,$name,$email,$text){ $this->id = $id; $this->moment = $moment; $this->name = $name; $this->email = $email; $this->text = $text; } Момент вообще лажовый. Добавлось у тебя еще в базе поле "Useragent". Тебе надо его дописать в 4 ! места: в $this->posts[$id] =, в параметры конструктора, в конструктор и в свойства класса. При этом ты порядок должен в уме помнить. Ппц просто. Почему бы не сделать хотя бы так: в классе Post сделать метод dbRow (array $row), которому передаётся результат строка с бд, это уменьшит количество мест, где надо дублировать до двух — только свойство класса и тело метода дбРов
и посмотри, какой у тебя распухший класс, несмотря на всю примитивность задания: в нём и настройки бд хранятся и информация о структуре сообщений и всё-всё-всё! Это — в отдельный класс вообще: Код (Text): private $tableName = null; //Имя таблицы для гостевой книги private $mysql_database = null; // База данных (может быть false если выбирать не надо) private $mysql_host = null; // Хост private $mysql_user = null; // Пользователь private $mysql_pass = null; // Пароль private $mysql_charset = null; // Кодировка Код (Text): function setDbParams а если я хочу гостевой передать уже открытое соединение? или обязательно для неё создавать отдельное соединение? а вообще, это ужасный подход: PHP: <?php function setDbParams($mysql_host,$mysql_user,$mysql_pass, $mysql_database,$mysql_charset, $tableName) // Лучше: function dbConfig(array $config) // Где $config — ассоциативный массив Почему не указываешь область видимости метода (паблик, приват, протектед)? Почему не написал интерфейсов, чтобы я мог, не глядя в код, работать с Апи?
в тег php не стал обрамлять так он убил все двойные знаки равно. Код бы написал как раз для таких комментариев как твой, рассуждать о существуешем, даже очень говеном коде продуктивней,чем рассуждать о нужно ли это API или нет.Я специально показал внутриности,а не внешнюю оболочку чтобы люди высказали что правильно, а что заблуждение. PS:Не отрицаю своего очень плохого понимания ООП, и как в частности ООП в php.
У меня ничего не убивает: PHP: <?php function getPostsCount(){ //Кол-во постов в базе $this->initDb(); if ($this->postCounts == null) { $countsArray = $this->db->query('SELECT COUNT(*) as counts FROM `'.$this->tableName.'`'); $this->postCounts= intval($countsArray[0]['counts']); } return $this->postCounts; } Я тебе рассказал: подход неправильный. Надо начать с тестов (интерфейсов), а потом под тесты уже писать начинку. Я тебе написал уже пару примеров. Могу еще пример написать: PHP: <? $posts = $forum->search(array( 'catId' => $_GET['cat'], 'query' => $_GET['q'], 'author' => $_GET['user'] )); Найдёт все сообщения пользователя с айди $_GET['user'] в категории $_GET['cat'], в которых есть текст $_GET['q']. И когда напишешь удобные тесты на все действия — вот тогда и обсудим, удобно оно, или нет. Но не забудь ничего: удаление и редактирование админом сообщений, добавление сообщений, пейджер (это только гостевая, для форума их намного больше). При чем конфиг апи должен быть внешним. Я не должен лезть в класс, чтобы поменять количество сообщений на странице. Это должно выглядеть как-то так (минимум): [/php]<?php $guest = new Api_Guest (array( 'postsPerPage' => 15, 'maxPostLength' => 4096, 'enableBBCode' => true, 'db' => array ( 'engine' => 'mysql', 'host' => 'localhost', 'user' => 'Shock', 'pass' => 'qwerty', 'table' => 'mysite', 'prefix' => 'api_guest', 'charset' => 'utf-8', ) ));[/php] В качестве альтернативы настроки должны быть такие, чтобы я мог подключить уже существующее соединение: PHP: <?php 'db' => array ( 'resourse' => $dbResourse, 'table' => 'mysite', 'prefix' => 'api_guest' ) Пишешь список таких тестов, красиво оформляешь, пишешь _интерфейсы_ — тогда выкладываешь на общий розсуд. Если интерфейс нам понравится — можешь начинать писать под него код. Но не надо нас больше просить оценить core-код, пока не согласуешь интерфейс с нами Как я уже сказал — я прекрасно понимаю, что планировать — оно нифига неинтересно, а гараздо интереснее сразу сесть и писать, но так — нихрена не получится, увы.
разрешаю, приступай. а, чтобы больше не подъёбывал, объясняю: мне лично совершенно неинтересно смотреть непонятно какой код. покажи удобный интерфейс и если он мне понравится, то я помогу и буду комментировать сообственно код. а смотреть на то, как ты балуешься, ни у меня ни, я думаю, у кого-либо другого на этом форуме желания нету.