Своя система голосования

Один из самых частых вопросов начинающих веб-программистов является вопрос «А как сделать онлайн голосование?». Очень просто! И даже если сейчас Вам так не кажется, уверяю Вас, стоит лишь вдумчиво прочитать данную статью, и Вы забудете про вопросы о голосования раз и навсегда.

Предисловие : если вдруг Вы являетесь не новичком в PHP, советую не кидать статью дочитав до середины, чертыхаясь на автора, а прочитать данный небольшой материальчик полностью.

Для начала давайте определимся, что нам нужно для создания голосования.

Итак, первое что понадобиться — форма для отображения пунктов голосования и передачи результатов на сервер соответствующему скрипту для сохранения. Сразу выделяются два новых объекта — скрипт для приема, обработки и сохранения результатов и место для хранения этих самых результатов голосования.

Местом для хранения данных спокойно может быть обычный файл. Чего-то не хватает? Ах, да! Нам же нужно еще отображать результаты голосования. Эту функцию можно спокойно исполнить в нашем едином скрипте.

Ну что же, приступим. Начнем, конечно же, с формы для отображения пунктов голосования и передачи данных на сервер. В нем нет ничего сложного:

Как Вам мой сайт?
Супер!
Хорошо
Нормально
Слабо
Лажа!

Как Вы видите, обычная форма, которая передает данные методом POST скрипту golos.php, имеется 5 вариантов ответа, кнопка Голосовать — для голосования и Результаты — просмотр результатов голосования без участия в самом голосовании.

Что дальше? Скрипт? Нет, в начале нужно определиться как мы будим хранить данные. Первое что приходит в голову — в каждой строчке по значению. Довольно удобно — все сразу можно считать в массив. Однако, не стоит забывать что при этом первая строка примет индекс 0 — тут начинающие программисты часто путаются. Я советую не использовать первую строку для хранения результатов голосования, а написать там какой-нибудь комментарий, например, что это файл для хранения результатов такого-то голосования.

Спасибо, Ваш голос принят"; //увеличиваем на 1 соотв. пункт голосования: $a[$otvet]++; //и общее количество голосов: $n++; //подготавливаем данные для записи в файл: $rez = "Опрос на сайте - это комментарий в файлеn".$a."n".$a."n".$a."n".$a; //открываем файл для записи: $fp = @fopen($file,"w"); //если успешно: if ($fp) { //записываем данные в файл: fputs($fp,$rez); //и закрываем его: fclose($fp); } //если произошла ошибка: else { echo "Ошибка при открытии файла"; } } //выводим результаты: else { echo "
Результаты голосования"; } echo "
Супер! - ".$a.""; echo "
Хорошо - ".$a.""; echo "
Нормально - ".$a.""; echo "
Слабо - ".$a.""; echo "
Лажа! - ".$a.""; echo "

Всего голосов: ".$n; ?>

Сразу скажу, что данный скрипт я нашел в кодах одного из моих знакомых, считающего себя большим спецом. Данный скрипт стоит у него на сайте.

Ну что же. Вот и все! Вы уже побежали ставить себе на сайт голосование? А зря! Ведь данный код лишь демонстрация алгоритма, слабого алгоритма, я уже молчу про возможности самого кода. Он даже не сможет обеспечить надежной работы — при большом количестве обращений файл голосований будет периодически обнуляться. Или, например, у нас нет абсолютно никакой защиты от многократных голосований — один человек может голосовать столько раз, сколько ему захочется.

Я уже молчу про недоработанный алгоритм скрипта — посмотрите в первой части мы очень неплохо используем цикл, который не зависит от количества пунктов голосования, а вот при выводе результатов — мы жестко привязаны к этому числу. А вывод результатов? Куда это годиться…

В общем, продолжать перечислять недостатки скрипта можно долго.

Хорошо Плохо

Вы когда-нибудь хотели узнать мнение своих посетителей по тому или иному поводу? Например, "что Вы хотите видеть на сайте" или "нравиться ли Вам наш новый дизайн" ? Уверен, что такое желание возникало. Действительно, установка на сайте собственного опроса или системы голосования позволяет не только копить бесценную информацию о том, что думают Ваши посетители, но и придать сайту нелишнюю интерактивность.

В общем, достоинства собственной системы голосования очевидны. Но что делать, когда под рукой нет необходимого скрипта? Можно, конечно, воспользоваться бесплатными системами голосования, которые позволяют Вам установить форму с несколькими вопросами на свой сайт. В этом случае сам скрипт находиться на удаленном сервере и выполняет всю работу. Но, на мой взгляд, это не очень подходящий вариант: во-первых, эти системы вставляют свой баннер в голосование, во-вторых, работоспособность системы от Вас совершенно никаким образом не зависит. Также, некоторые системы требуют довольно продолжительного нахождения в Сети, поскольку вся настройка голосования происходит обычно on-line. А для некоторых пользователей это совсем немаловажный момент.

Итак, лучше всего создать свой собственный скрипт. Конечно, это потребует от Вас кое-какой работы, но зато с лихвой окупиться за счет полной конфигурируемости, тонкости настройки и полностью своего интерфейса. Да и позволит лишний раз попрактиковаться в программировании, что обычно не является лишним занятием для труженика сети:)

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

Все, что от Вас потребуется - это начальные (буквально азы) знания PHP и хостинг с поддержкой этого языка. Данный скрипт сейчас успешно работает на площадке провайдера http://www.eServer.ru .

Итак, начнем.

Давайте представим, что мы на сайте хотим объявить следующее голосование:

ВАШЕ МНЕНИЕ: Как Вам наш новый дизайн? 5 - Отлично! 3 - Так себе, потянет... 1 - Ужасно!!!

Пусть Вас не смущает количество вариантов ответов, на конфигурируемости это никак не отражается. Вы сможете задать любое количество вопросов (равно как и любой вопрос в принципе) - от одного и до бесконечности безо всяких проблем.

Как Вам наш новый дизайн?

Отлично!
Так себе, потянет...
Ужасно!!!

Это все. Теперь можно приступить к самому скрипту. Как видно из формы, нашему скрипту передается две переменных:

  • id=1: это номер голосования. Дальше Вы увидите, как он используется для создания нескольких независимых голосований.
  • vote=1: номер ответа, то есть конкретно выбранный вариант ответа Вашим посетителем.

Также к нашему скрипту ведет ссылка "Текущие результаты", указывающая только параметр id, без номера ответа. Дело в том, что скрипт при отсутствии параметра vote просто выводит результаты, без сопутствующей благодарности "Спасибо за Ваше мнение!" и каких-либо действий по учету голосования.

Продолжаем. Нашему скрипту потребуется собственная база данных с текстами вопросов и ответом к ним. Конкретное голосование - это один вопрос и набор ответов к нему, а также количество мнений на каждый ответ. Все голосования хранятся в отдельных текстовых файлах и определяются по переменной id, передаваемой через форму. В примере id - это цифра (или что угодно другое), которая является первой частью имени файла. Для примера, если id=1, значит база данных голосования лежит в таком файле: 1.dat или, в терминах PHP, $id.dat.
Впрочем, Вы увидите это далее на конкретном примере. Отсюда получается, что для создания голосования Вам достаточно создать новый файл в очень простом формате (см. далее) и html-форму, содержащую тексты вопросов и id голосования.

Итак, раз уж мы с Вами определились, что данные голосования хранятся в отдельных файлах, давайте рассмотрим формат конкретного файла. Он имеет такой вид:

Как Вам наш новый дизайн? 5~Отлично! 3~Так себе, потянет... 1~Ужасно!!!

Где 1я строка: это сам вопрос, остальные строки - пара "количество_мнений~ответ", как Вы поняли, разделенных тильдой "~". Теперь все, что нам осталось, это обрабатывать результаты.

Это делает следующий скрипт:

// файл с конкретным голосованием $data = File("$id .dat"); // выводим благодарности, // если это не просто просмотр результатов
if ($vote ) echo " Спасибо за Ваше мнение!

"; // выводим заголовок голосования - 1я строка файла echo "$data

"; // печатаем список ответов и результатов - остальные строки for ($i =1;$i // значение~ответ echo "$votes : $votes
"; } // если это не просмотр результатов, а голосование, // производим необходимые действия для учета голоса if ($vote ) { $f = fopen("$id .dat","w"); fputs($f , "$data "); for ($i =1;$i

Собственно, это все, что требуется. Как видите, скрипт достаточно прост, не выполняет каких-либо специфический действий, да и ресурсов много не потребляет. Конечно, сюда можно добавить многие другие полезные функции, например, сделать администраторский интерфейс для управления голосованиями через броузер и держать все данные по голосованиям в одном файле, чтобы не возиться каждый раз с $id.dat. Но это - в следующий раз, а пока моей целью было показать Вам, что столь полезная система голосования очень просто реализуется.

    См. окончательный... Словарь русских синонимов и сходных по смыслу выражений. под. ред. Н. Абрамова, М.: Русские словари, 1999. бесповоротный безвозвратный, окончательный, отличительный, решительный; конечный, последний … Словарь синонимов

    БЕСПОВОРОТНЫЙ, чего нельзя повернуть, что не перевертывается; | невозвращаемый, не отдаваемый обратно, безобратный, безвозвратный. Отдай должок; аль ты брал бесповоротно? Бесповоротность, безвозвратность. Толковый словарь Даля. В.И. Даль. 1863… … Толковый словарь Даля

    БЕСПОВОРОТНЫЙ, бесповоротная, бесповоротное; (кратк. мужск. не употр.) бесповоротна, бесповоротно. Окончательный; такой, который не будет изменен. Бесповоротное решение. Толковый словарь Ушакова. Д.Н. Ушаков. 1935 1940 … Толковый словарь Ушакова

    Прил. Такой, который не будет изменен; окончательный. Толковый словарь Ефремовой. Т. Ф. Ефремова. 2000 … Современный толковый словарь русского языка Ефремовой

    Бесповоротный, бесповоротная, бесповоротное, бесповоротные, бесповоротного, бесповоротной, бесповоротного, бесповоротных, бесповоротному, бесповоротной, бесповоротному, бесповоротным, бесповоротный, бесповоротную, бесповоротное, бесповоротные,… … Формы слов

    бесповоротный - бесповор отный; кратк. форма тен, тна … Русский орфографический словарь

    бесповоротный - кр.ф. бесповоро/тен, бесповоро/тна, тно, тны … Орфографический словарь русского языка

    бесповоротный - Syn: окончательный, отличительный, решительный (редк.) … Тезаурус русской деловой лексики

    Ая, ое; тен, тна, тно. Такой, который не будет изменён; окончательный. Б ое решение. ◁ Бесповоротно, нареч. Отказать решительно и б. Бесповоротность, и; ж … Энциклопедический словарь

    бесповоротный - ая, ое; тен, тна, тно. см. тж. бесповоротно, бесповоротность Такой, который не будет изменён; окончательный. Б ое решение … Словарь многих выражений

Книги

  • Жизнь Петра Великого , . Редкость! Санкт-Петербург, 1890 год. Издание редакции журнала "Досуг и дело" . Иллюстрированное издание. Владельческий переплет. Кожаный бинтовой корешок. Сохранностьхорошая. Преобразования…
  • Царица Евдокия, или Плач по Московскому царству , Козляков Вячеслав Николаевич. Последняя московская царица, отвергнутая жена царя Петра I, монахиня Елена, заточенная сначала в Суздальском Покровском монастыре, а затем в бастионе Шлиссельбургской крепости, и, наконец,…

Здравствуйте. Предлагаю Вашему вниманию инструкцию по созданию системы голосования на сайте.
Итак, давайте рассмотрим, что же представляет из себя система голосования на сайте.
Пользователь видит на странице форму, в которой присутствуют заголовок голования, варианты ответов, из которых можно выбрать один, тот, за который пользователь хочет проголосовать.
После выбора пользователь нажимает кнопку "проголосовать" и система засчитывает его голос в пользу того или иного варианта ответа.
"Снаружи" вроде всё просто: обычная форма, обычная кнопка, обычные radio-кнопки. Но давайте заглянем "за кулисы" работы скрипта голосования. Там нашему взору откроется более интересная картина.

Что же представляет из себя система голосования изнутри?
Как можно запоминать выбор пользователя и не давать ему голосовать повторно?
Как создавать вопросы и варианты ответов?
На эти вопросы мы сейчас попробуем получить развёрнутый ответ. Нам нужно где-то хранить вопросы и варианты ответов. Для этого нам потребуется создать базу данных MySQL и пару таблиц в ней.
Давайте создадим такие таблицы:
voting - таблица, в которой будут храниться вопросы и варианты ответов.
voted - таблица, в которой будут храниться выбранные варианты ответов и IP адреса проголосовавших.

В таблице voting нам нужно создать три поля:
1. id - тип INTEGER, AUTOINCREMENT.
В поле будет содержаться идентификатор вопросов и ответов.
2. parent_id - тип INTEGER.
В поле будет содержаться идентификатор родительской записи (т.е. по отношению к ответам, родительской записью будет вопрос).
3. title - тип VARCHAR.
В поле будет содержаться текст вопросов и ответов.

Как видите, мы будем хранить вопросы и ответы в одной таблице. Поскольку нам в данном случае нужна связь один-ко-многим и поля, необходимые для хранения вопросов и ответов одинаковые,- это очень удобный вариант.
Тут плюс в том, что не нужно создавать отдельные таблицы для вопросов и ответов, и запросов в БД нужно будет делать меньше.

В таблице voted нам нужно создать два поля:
1. answer_id - тип INTEGER.
В поле будет содержаться идентификатор выбранного пользователем ответа (т.е. ответа, за который он проголосовал).
2. ip - тип VARCHAR. UNIQUE.
В поле будет содержаться IP-адрес пользователя, который проголосовал. В данном поле будут только уникальные значения IP.

На этом моменте остановимся поподробнее. Почему мы запоминаем IP пользователя? Ведь IP бывают динамическими, скажете Вы. И пользователь, сменив IP, сможет проголосовать несколько раз. Я с Вами полностью согласен, но ещё не придумано 100% надёжного варианта запонимания неавторизованного пользователя.
Почему я упомянул авторизацию: если мы хотим, чтобы пользователь гарантированно не смог проголосовать несколько раз, нам нужно сделать систему авторизации и заместо IP пользователя использовать для учёта его голоса идентификатор его аккаунта.
И сделать открытым голосование только для авторизованных пользователей. Но создание голосования для авторизованных не входит в планы данной статьи, поэтому для учёта голоса будем запоминать IP.

Итак, мы создали таблицы, теперь давайте перейдём непосредственно к реализации php-скрипта системы голосования. Давайте разберём вот такой класс:

db = new PDO($dsn, "root", ""); # Устанавливаем ID голосования $this->id = $id; } /** * Для выборки из базы голосования по установленному ID * * @return заполняет массив $this->result данными */ private function selectVoting(){ # SQL-запрос для выборки опроса $stmt = $this->db->prepare("SELECT id, title, 0 as voted FROM `$this->tbl_voting` WHERE id=:id AND parent_id=0 UNION SELECT o.id, o.title, COUNT(v.ip) FROM `$this->tbl_voting` o LEFT JOIN `$this->tbl_voted` v ON v.answer_id = o.id WHERE parent_id=:id GROUP BY o.id"); # Выполняем запрос $stmt->execute(array(":id" => $this->id)); # Получаем ассоциативный массив $this->result["voting"] = $stmt->fetchAll(PDO::FETCH_ASSOC); # Если данных нет if(!$this->result) # Бросаем исключение throw new Exception("Голосования с ID ". $this->id ." нет"); } /** * Для проверки, голосовал ли уже пользователь * * @return */ private function checkAlreadyVoted(){ # SQL-запрос для выборки опроса $stmt = $this->db->prepare("SELECT COUNT(*) FROM `$this->tbl_voted` WHERE ip=:ip AND answer_id IN(SELECT id FROM `$this->tbl_voting` WHERE parent_id=$this->id)"); # Выполняем запрос $stmt->execute(array(":ip" => $this->getIP())); # Записываем полученные данные в конечный массив $this->result["already_voted"] = (bool) $stmt->fetch(PDO::FETCH_COLUMN); } /** * Для разбора массива, установленного в select() * * @return заполняет массив готовыми для шаблона данными */ private function prepare(){ # Получаем из массива первый элемент (вопрос) $ask = array_shift($this->result["voting"]); # Записываем готовые данные в итоговый массив $this->result = array("title" => $ask["title"], "options" => $this->result["voting"], "already_voted" => $this->result["already_voted"]); } /** * Для получения IP-адреса * * @return IP-адрес пользователя */ private function getIP(){ return getenv("REMOTE_ADDR"); } /** * Для получения массива с информацией о голосовании * @param integer $id - идентификатор голосования, * которое нужно вывести * * @return */ public function get(){ # Вызываем метод выборки данных из БД $this->selectVoting(); # Вызываем метод проверки, голосовал ли пользователь $this->checkAlreadyVoted(); # Вызываем метод обработки полученных данных $this->prepare(); # Возвращаем массив данных return $this->result; } /** * Для добавления голоса к опросу * * @return */ public function add($id){ # Подготавливаем запрос для добавления голоса $stmt = $this->db->prepare("INSERT IGNORE INTO `$this->tbl_voted` VALUES ((SELECT id FROM `$this->tbl_voting` WHERE id=:id), :ip)"); # Выполняем запрос $stmt->execute(array(":id" => $id, ":ip" => $this->getIP())); } }


Класс довольно неплохо прокомментирован, поэтому разберём только самые интересные методы данного класса. Для начала нам нужно настроить соединение с нашей базой банных. Для этого в конструкторе нужно указать хост, имя базы данных, имя пользователя и пароль.
Как видите, в мы используем для работы с базой данных расширение PDO. С этим расширением очень удобно работать, и при правильном составлении запросов ещё и безопасно, никакие SQL-инъекции не будут страшны.
Если у Вас возникли затруднения в настройке соединения с базой данных, обратитесь к документации PDO:

Теперь давайте разберём метод выборки вопроса и соответствующих ему ответов:

private function selectVoting(){ # SQL-запрос для выборки опроса $stmt = $this->db->prepare("SELECT id, title, 0 as voted FROM `$this->tbl_voting` WHERE id=:id AND parent_id=0 UNION SELECT o.id, o.title, COUNT(v.ip) FROM `$this->tbl_voting` o LEFT JOIN `$this->tbl_voted` v ON v.answer_id = o.id WHERE parent_id=:id GROUP BY o.id"); # Выполняем запрос $stmt->execute(array(":id" => $this->id)); # Получаем ассоциативный массив $this->result["voting"] = $stmt->fetchAll(PDO::FETCH_ASSOC); # Если данных нет if(!$this->result) # Бросаем исключение throw new Exception("Голосования с ID ". $this->id ." нет"); }


Как видите, здесь мы используем prepared statement (подготовленные запросы) PDO. В самом запросе мы указываем якорь ":id", а затем в методе "execute" указываем, каким значением заменить данный якорь.
В данном SQL запросе мы используем объединение результатов запросов помощью UNION. Первым SELEСЕ"ом мы выбираем необходмый вопрос (идентификатор которого содержится в свойстве "id" данного класса), а
вторым выбираем принадлежащие данному вопросу варианты ответов и количество голосов за определённыый вопрос (для вывода статистики).
В результате данного запроса мы получаем массив, первым элементом которого является вопрос, а остальными элементами - варианты ответа на вопрос. Теперь нам нужно отделить вопрос от ответов. Для этого мы используем метод "prepare":

/** * Для разбора массива, установленного в select() * * @return заполняет массив готовыми для шаблона данными */ private function prepare(){ # Получаем из массива первый элемент (вопрос) $ask = array_shift($this->result["voting"]); # Записываем готовые данные в итоговый массив $this->result = array("title" => $ask["title"], "options" => $this->result["voting"], "already_voted" => $this->result["already_voted"]); }


C помощью array_shift мы вырезаем вопрос из массива и вставляем текст вопроса в результирующий массив (который передадим уже на вывод). Как видите, здесь встречается такой элемент, как "already_voted".
В нём мы содержим информацию, голосовал ли пользователь ранее, т.е. содержится ли IP пользователя в таблице "voted". Проверка на наличие IP в таблице производится в методе "checkAlreadyVoted".

Ну что, с данным классом мы разобрались, теперь сохраните его в файл (назовите его "voting.class.php") и скопируйте к себе на сервер. Далее давайте разберём, как мы будем выводить голосование на экран.
Сначала нам нужно подключить вышеописанный класс на страницу, где будем выводить голосование. Я не знаю, каким образом работает Ваша CMS, поэтому давайте подключим, используя обычный "require":

require("путь к файлу/voting.class.php");


Отлично, класс подключён. Можно с ним работать. Но у нас пока нет ни одного голосования,- давайте создадим его. Для этого нужно зайти в phpMyAdmin, выбрать таблицу "voting" и вставить в неё вопросы и ответы.
Допустим, мы хотим узнать от пользователей, как они относятся к нашему сайту. Для этого в поле "title" вставим такой вопрос: "Как Вам наш сайт?". В поле "parent_id" нужно вставить "0", так как это вопрос и он не является дочерней записью другого вопроса.
После сохранения мы видим, что у нас появилась новая запись в таблице. Теперь мы знаем её идентификатор (находится в поле "id"), это и есть идентификатор вопроса, по этому идентификатору мы привяжем к вопросу варианты ответов на него.
Теперь создадим варианты ответов, для этого в поле "title", например впишем "Отлично". Эта запись и будет одним из вариантов ответа. В поле parent_id нам нужно указать идентификатор вопроса, к которому принадлежит данный ответ.
Таким образом создайте необходимое количество вариантов ответов, не забывая в поле parent_id указывать идентификатор вопроса.

Что ж, мы успешно создали вопрос и варианты ответов на него в базе данных. Теперь перейдём к выводу голосования на страницу.
Для того, чтобы вывести необходимое голосование, нам нужно создать экземпляр объекта класса, который мы подключили на страницу ранее:


В коде выше мы видим, что у нас создаётся объект класса, в качестве аргумента конструктору которого передаётся ID вопроса. Как видите, нам не нужно перечислять ID всех записей в БД, принадлежащих данному голосованию. Достаточно указать лишь ID вопроса, а дочерние записи (т.е. у которых в поле "parent_id" содержится ID данного вопроса)
подтянутся вместе с ним, с помощью UNUON, о котором мы говорили ранее. Сейчас мы передаём ID "1", если у Вашего вопроса идентификатор другой - передайте его, единица здесь только для примера.
Если Вы всё правильно сделали, в итоге, в переменной $data у Вас будет примерно такой массив:

array(3) { ["title"]=> string(30) "Как Вам наш сайт?" ["options"]=> array(3) { => array(3) { ["id"]=> string(1) "2" ["title"]=> string(27) "Первый вариант ответа" ["voted"]=> string(1) "0" } => array(3) { ["id"]=> string(1) "3" ["title"]=> string(13) "Второй вариант ответа" ["voted"]=> string(1) "0" } => array(3) { ["id"]=> string(1) "4" ["title"]=> string(27) "Третий вариант ответа" ["voted"]=> string(1) "0" } } ["already_voted"]=> bool(false) }


Теперь нам нужно создать форму, в которой это голосование будет отображаться. Напишем такой код (код просто для примера, Вы можете стилизовать его под свой сайт, добавив необходимые теги):

- голосов


Как мы видим, в коде проводится проверка, голосовали ли пользователь ранее:


Если пользователь уже голосовал (т.е. его IP есть в таблице voted ), то ему выводится на экран статистика голосов, т.е. ответы и количество пользователей, проголосовавших за тот или иной ответ. Тут Вы можете подключить фантазию и доработать скрипт, например, сделав вывод статистики в виде графика.
Если же пользователь не принимал участие в данном голосовании, то ему выводится форма, в которой он может выбрать необходимый вариант ответа и проголосовать за него.
В цикле мы проходим по всем вариантам ответов и выводим их на экран. В качестве значения в поля "radio" вставляются их идентификаторы:

"/>


Допустим, пользователь выбрал необходимый ответ и нажал "Голосовать". После этого он перейдёт к скрипту "voted.php", как мы видим в коде, "action" формы ведёт именно на этот скрипт:


Теперь создадим файл "vote.php" и напишем в нём такой код:

add($id); # Перенаправляем назад header("Location: " . getenv("HTTP_REFERER")); } } catch(Exception $e){ # Выводим сообщение echo $e->getMessage(); }


Как видите, в нём мы тоже подключаем класс голосования. Затем проводится проверка, нажата ли кнопка "Голосовать". Если нажата - получаем ID ответа, за который голосует пользователь.
Так же создаётся экземпляр класса голосования, только теперь мы не передаём ID вопроса в конструктор, тут он не требуется, так как опрерируем на данном этапе только с ответом.
Затем мы вызываем метод "add", передавая в него идентификатор вопроса:

$voting->add($id);


В методе "add" мы используем INSERT IGNORE для того, чтобы IP пользователя повторно не записывался в таблицу, ведь лишние записи нам ни к чему.
После того, как голос пользователя учтён или проигнорирован (в случае, если IP уже есть в таблице), идёт перенаправление его на предыдущую страницу:

header("Location: " . getenv("HTTP_REFERER"));


Ну вот и всё, голосование готово. Её можно ещё конечно доработать, добавив возможность создавать и удалять голосования из админки, добавив внешние ключи для таблиц, чтобы при удалении вопроса из базы удалялись и все связанные с ним вопросы и записи в таблице voted .
Но это уже на Ваше усмотрение.