register

Давайте предоставим новым пользователям возможность регистрироваться. Перейдите в ~/vhosts/pset7/templates в «Виртуальной лаборатории» или ~/workspace/pset7/views и выполните команду:

cp login_form.php register_form.php

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

Затем откройте register_form.php и измените значение атрибута action формы с login.php на register.php. Затем добавьте дополнительное поле типа password в HTML-формы на имя confirmation, чтобы пользователи имели вводить пароль дважды (во избежание ошибок). Наконец, измените текст кнопки с Log In на Register и измените строку с такой:

or <a href="register.php"> register  for an account

на такую:

or <a href="login.php"> log in

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

Затем создайте новый файл по имени register.php и внесите в него следующее содержание:

<? Php
    // configuration
    require ("../ includes / config.php");
    // if user reached page via GET (as by clicking a link or via redirect)
    if ($ _SERVER ["REQUEST_METHOD"] == "GET")
    {
        // else render form
        render ("register_form.php" ["title" => "Register"]);
    }

    // else if user reached page via POST (as by submitting a form via POST)
    else if ($ _SERVER ["REQUEST_METHOD"] == "POST")
    {
        // TODO
    }
?>

Не забудьте сохранить файлик в pset7/public.

Теперь давайте посмотрим на наш труд! Перейдём по ссылке http://pset7/login.php (в Chrome «Виртуальной лаборатории») или https://ide50-username.cs50.io/login.php, если вы работаете в CS50 IDE (username — это ваше имя, помните?), и кликнете на странице по ссылке register.php. После этого вы окажетесь на http: //pset7/register.php. Если что-то пошло не так, перейдите назад на register_form.php или register.php. Только будьте уверены, что сохранили ваши изменения и перезагрузили страницу в браузере.
Конечно, register.php еще не регистрирует пользователей, так что давайте выполним этот пункт TODO! Вот вам несколько подсказок:

  • Если $ _POST ["username"] или $ _POST ["password"] пустые, или $ _POST ["password"] не равно $ _POST ["confirmation"], вам нужно проинформировать пытающихся зарегистрироваться пользователей об их ошибках.
  • Чтобы добавить нового пользователя в вашу базу данных, используйте команду.
query ("INSERT INTO users (username, hash, cash) VALUES (?,?, 10000.00)", $ _POST [ "username"], crypt ($ _ POST ["password"]));

или

CS50::query("INSERT IGNORE INTO users (username, hash, cash) VALUES(?, ?, 10000.0000)", $_POST["username"], password_hash($_POST["password"], PASSWORD_DEFAULT));

А вот сколько денег ваш код положит на аккаунт пользователя — выбор за вами!

  • Функция query вернет false, если INSERT будет выполнен неудачно (такое может произойти, например, если username уже существует). Убедитесь, что вы проверяете результат на false с помощью ===, а не ==.
  • Если ваш INSERT пройдет удачно, можно выяснить, какой id был назначен этом пользователю с помощью примерно следующего кода:
  • $ Rows = query ("SELECT LAST_INSERT_ID () AS id");
    $ Id = $ rows [0] [ "id"];
  • Если регистрация пройдет успешно, вы можете залогинить нового пользователя («запомнив» его id в $ _SESSION), и затем перенаправить его на index.php.

Всё ли сделано в register.php? Готовые тестировать? Вернитесь к http: //pset7/register.php (в «Виртуальной лаборатории») или https://ide50-username.cs50.io/register.php (если вы всё настраивали с помощью CS50 IDE) и попробуйте зарегистрировать новое имя.

Если вы добрались до index.php, высока вероятность, что у вас всё сделано правильно! Убедитесь в этом, вернувшись к phpMyAdmin и снова кликнув на вкладку Browse (Просмотреть) таблицы users. Возможно, вы увидели своего нового пользователя. Если нет, надо «дебажить»!

Убедитесь, что весь HTML, сгенерированный register.php, — корректен: кликните по странице в Chrome левой кнопкой с Ctrl или правой кнопкой, выберите View Page Source (Смотреть код страницы), выберите и скопируйте исходный код, вставьте его в валидатор на http://validator.w3.org/#validate_by_input и кликните Check (Проверить). Результат проверки вашей страницы должен быть Passed (Пройдено) или Tentatively passed (в основном пройден). В таком случае вы увидите «дружественный» зелёный баннер. Предупреждения (Warnings) — это нормально. А вот ошибки в совокупности с большим красным баннером — не очень. Заметьте, что вы не сможете «проверить с URI» на http://validator.w3.org/#validate_by_uri, так как ваша программа не лежит в общем доступе в интернете.

Советуем вам по мере прохождения курса поиграть и поучиться с нашим вариантом C$ 50 Finance на https://finance.cs50.net/. В частности, чтобы поиграть, можете зарегистрировать несколько придуманных имен, и посмотреть HTML и CSS наших страниц (взглянув на код страницы с помощью браузера), чтобы научиться и улучшить ваш собственный дизайн. Если хотите, свободно используйте наш HTML и CSS как ваш собственный.

Только не копируйте наш дизайн, лучше пофантазируйте. Для этой задачи вы свободно можете изменять любые файлы по вашему вкусу и воплощать свое видение. Пусть ваша версия C$ 50 Finance будет даже лучше нашей!

quote

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

Как найти последнюю цену акции? Вспомните функцию под названием lookup в файле functions.php. Возможно, вы захотите использовать ее следующим образом:

$ Stock = lookup ($ _ POST ["symbol"]);

Если значение $ _POST [ "symbol"] содержит корректный символ акции, lookup вернет ассоциативный массив с тремя ключами для этой акции: symbol (символ), name (имя) и price (цена). Вы можете использовать функцию PHP number_format для форматирования цены, чтобы она содержала от двух до четырех десятичных знаков. Подробнее смотрите на http://php.net/manual/en/function.number-format.php.

Конечно, если пользователь прислал некорректный символ (для которого lookup вернет false), не забудьте сообщить ему об этом. Также убедитесь с помощью валидатора W3C, что все созданные вашими шаблонами HTML — корректны.

portfolio

Давайте обратим внимание на схему базы. Сейчас ваша база данных не может отслеживать портфолио пользователей, только их самих. Под «портфолио» мы понимаем в виду акции (доли компаний), которыми владеют пользователи. Имеет ли смысл добавлять поля в таблицу пользователей для того, чтобы отслеживать их акции (скажем, использовать одно поле для каждой компании)? Вряд ли. В конце концов, никто не знает, сколько акций различных компаний может быть у одного пользователя. Лучше хранить эти данные вместе в отдельной таблице: так мы не будем накладывать ограничения на портфолио пользователей и тратить место на поля, которыми, возможно, не будем пользоваться.

Что именно нам нужно хранить в новой таблице, чтобы «помнить» портфолио пользователей?

  • Вероятно, нам понадобится поле идентификатора (id), которое однозначно идентифицирует строки (в качестве первичного ключа таблицы).
  • Нам не помешает поле для идентификации пользователей, чтобы у нас была возможность перекрестной ссылки на записи самих пользователей (users). Назовём его user_id, чтобы было ясно, что это внешний ключ (то есть, первичный ключ другой таблицы).
  • Логично было бы отслеживать акции по их сокращённым названиям (символам, как на бирже): обычно они короче (следовательно, эффективнее сохраняются), чем полные наименование акций. Разумеется, вы можете назначить акциям уникальные цифровые идентификаторы и хранить их вместо символов. Однако в таком случае вам нужно будет поддерживать вашу собственную базу данных компаний, время от времени обновляя ее, скажем, по данным Yahoo. Так что лучше (и, уж точно проще!) будет отслеживать акции по их символам. Это поле можно назвать symbol.
  • Также интересна информация о том, каким паем тех или иных акций владеет пользователь. Поле shares.

Таким образом, логично будет создать табличку с четырьмя полями (id, user_id, symbol и shares). Однако если вам хочется сделать иначе и придумать свою схему — это тоже неплохой вариант.

Что бы вы ни решили, возвращайтесь в phpMyAdmin и создайте там новую таблицу. Назовите её как вам захочется. Чтобы создать новую таблицу, кликните pset7 в верхнем левом углу phpMyAdmin, и на появившемся экране введите имя таблицы и количество колонок в форме Create table (Создать таблицу), затем нажмите Go (Далее). После этого появится экран с полями. Внесите туда ваши поля в любом порядке.

Если вы решили использовать перечисленные нами три поля (id, symbol и shares), нужно осознать, что поле user_id не должно быть первичным ключом таблицы (UNIQUE), иначе у каждого пользователя могут быть акции только одной компании (потому что его или её id не сможет появиться (как user_id) более чем в одной строке). Также важно понимать, что определенные user_id и symbol не должны появляться вместе более чем в одной строке. Лучше объединять вклады пользователей, обновляя паи, когда кто-то покупает или продает доли, которыми он уже владеет.

Элегантным способом ввести это ограничение при создании таблицы будет задача «объединенного первичного ключа», выбрав в качестве индекса Index одновременно user_id и symbol. После сохранения таблицы перейдите на вкладку «Структура» phpMyAdmin для таблицы, затем проверьте оба user_id и symbol, затем нажмите Unique справа от With selected.

В этом случае INSERT вернет ошибку, когда вы попытаетесь вставить более чем одну строку с парой определенных id и symbol.

Таким образом, INSERT не сработает, если вы попытаетесь вставить несколько строк для пары идентификаторов user_id и символа.) Однако мы оставляем за собой право решать ваши типы полей. (Просто знайте, что user_id в этой таблице должен иметь тип, идентичный id в users.

Тем не менее, вы вольны выбирать типы полей (только учтите, что user_id в этой таблице должен иметь тип, идентичный id в users.только знайте, что user_id Если вы включите поле id этой таблицы, его тип имеет совпадать с типом id в users. Но не указывайте AUTO_INCREMENT для этого поля в новой таблице, так как вы хотите автоматический инкремент только когда идентификаторы создаются для новых пользователей. После определения вашей таблицы, кликните Save (Сохранить)!

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

В этом интерфейсе «купите» несколько долей в акциях компаний, вставив строки в эту таблицу (возможно, вы захотите вернуться на Yahoo! Finance, чтобы взять актуальные символы). Не надо уменьшать cash в users, считайте эти акции подарком.

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

SELECT * FROM portfolios WHERE user_id = 9

Предположим, что число 9 — это user_id Президента Скруба. В таком случае такой запрос должен вернуть все строки таблицы portfolios, представляющие вклады Президента.

Если в вашей таблице есть только поля id, user_id, symbol и shares, то показанный выше запрос практически эквивалентен следующему:

SELECT id, user_id, symbol, shares FROM portfolios WHERE user_id = 9

Если же вам нужны только акции президента Скруба компании FreeSeas, выполните следующий запрос:

SELECT shares FROM portfolios WHERE user_id = 9 AND symbol = 'FREE'

Если перед этим вы «покупали» президенту Скрубу акции этой компании, вышеприведенное вернёт одну строку с одним столбцом — количеством акций. Если вы не покупали такие акции, запрос вернет пустой результат (то есть пустой массив).

Кстати, в этой вкладке SQL вы могли бы добавить эти «покупки» с помощью команды INSERT. Но графический интерфейс phpMyAdmin помогает вам сделать это проще.

Теперь давайте воспользуемся этими знаниями: предоставим пользователям возможность просмотреть их портфолио! Переделаем index.php (контроллер) и portfolio.php (шаблон, view) так, чтобы они сообщали не только о текущем денежном балансе, но также обо всех паях в портфолио пользователя, включая количество акций и их актуальную цену.

Излишне говорить, что в index.php надо будет вызвать lookup так же, как и в quote.php, а также делать это несколько раз. К вашему сведению, скрипт PHP может вызвать запрос query несколько раз, хотя до этого момента мы видели не более одного вызова на файл. И, разумеется, вы можете пройтись по возвращаемому им массиву в цикле во view (предполагая, что вы передали его с помощью render).

Например, если вы хотите показать паи Президента Скруба, по одному на строку в HTML-таблицы, можно сгенерировать строки таким кодом, где $ positions — массив ассоциативных массивов, каждый из которых представляет одну позицию (то есть набор акций).

<table>
<?php

        foreach ($positions as $position)
        {
            print("<tr>");
            print("<td>" . $position["symbol"] . "</td>");
            print("<td>" . $position["shares"] . "</td>");
            print("<td>" . $position["price"] . "</td>");
            print("</tr>");
        }

    ?>
</table>

Или же, можем обойтись без оператора конкатенации(.):

<table>
    <?php

        foreach ($positions as $position)
        {
            print("<tr&gut");
            print("<td>{$position["symbol"]}</td>");
            print("<td>{$position["shares"]}</td>");
            print("<td>{$position["price"]}</td>");
            print("</tr>");
        }

    ?>
</table>

Обратите внимание, в этой версии мы окружили строки HTML двойными кавычками вместо одинарных, чтобы интерполировать переменные внутри ($ position ["symbol"], $ position ["shares"] и $ position [ "price"]) (т.е. заменить своими значениями ) интерпретатором PHP. Переменные между одинарными кавычками не интерполируются. Кроме того, мы окружили эти переменные фигурными скобками, чтобы PHP признал в них переменные. Переменные с простым синтаксисом (например, $ foo) не нуждаются в фигурных скобках для интерполяции. Использовать двойные кавычки внутри фигурных скобок корректно, даже если мы также использовали двойные кавычки, чтобы окружить весь аргумент для print. Так или иначе, создание HTML с помощью вызовов print не слишком элегантно, хотя так делают часто. Ниже — альтернатива. Правда, тоже не самая элегантная.

<?php foreach ($positions as $position): ?>

    <tr>
        <td><?= $position["symbol"] ?></td>
        <td><?= $position["shares"] ?></td>
        <td><?= $position["price"] ?></td>
    </tr>

<?php endforeach ?>

Конечно, прежде, чем передать $positions к portfolio.php, надо определить их в index.php. Позвольте порекомендовать такой код, объединяющий имена и цены из lookup с частицами паев и символами, возвращены как $rows с query:

$positions = [];
foreach ($rows as $row)
{
    $stock = lookup($row["symbol"]);
    if ($stock !== false)
    {
        $positions[] = [
            "name" => $stock["name"],
            "price" => $stock["price"],
            "shares" => $row["shares"],
            "symbol" => $row["symbol"]
        ];
    }
}

Обратите внимание, с помощью этого кода мы намеренно создаем новый массив ассоциативных массивов ($positions) вместо того, чтобы добавлять имена и цены в существующий массив ассоциативных массивов ($rows). Для сохранения правильной структуры обычно лучше не изменять значения возвращаемых функций (например, $rows из query).

Теперь, точно так же, как передали заголовок страницы для рендеринга, можно передать информацию про акции:

render("portfolio.php", ["positions" => $positions, "title" => "Portfolio"]);

Конечно, вам также нужно передать текущий денежный баланс пользователя из index.php в portfolio.php с помощью render, но мы оставляем эту задачу вам.

Откровенно говоря, согласно «духу» MVC, наоборот, не следует вызывать lookup в этом (или любом другом) шаблоне. Его следует вызывать только в контроллерах. Невзирая на то, что шаблоны (они же views) могут содержать PHP-код, этот код следует использовать только для вывода и/или прохождения данных, полученных с контроллера (например, через render) в цикле.

Что касается HTML-кода для вдохновения, посмотрите на https://finance.cs50.net/. Только не чувствуйте себя обязанными подражать нашему дизайну, сделайте этот сайт своим! Только помните, что весь ваш HTML- и PHP-код, должен быть корректно отформатирован (то есть наделён правильными отступами). Не страшно, если длина строк превышает 80 символов, тем не менее, динамически генерируемый HTML-код (например, с помощью команды print) может быть не отформатирован.

Как и прежде, обратите внимание на то, чтобы цены акций и баланс средств пользователей содержали от двух до четырёх десятичных знаков.

Кстати, Президент Скруб у нас был введён для примера. Ваш код должен работать для любого пользователя, вошедшего в систему.
Как всегда, убедитесь, что HTML, сгенерированный index.php, корректен.

sell

Настало время реализовывать возможность продажи с помощью контроллера, называемого, скажем, sell.php и несколькими шаблонами (view). Разработку структуры этой функциональности разработайте самостоятельно. Но знайте, вы можете удалить строки из вашей таблицы (от имени, скажем, президента Скруба) с помощью такого SQL-запроса:

DELETE FROM portfolios WHERE user_id = 9 AND symbol = 'FREE'

Определите, что именно делает этот запрос. Конечно, можете выполнить его с помощью вкладки SQL в phpMyAdmin. Теперь о денежном балансе пользователя. Скорее всего, ваш пользователь захочет продать свои акции. Продажа акций предполагает обновление не только таблицы портфолио пользователей, но и таблицы с самими пользователями (users). Определитесь с тем, сколько денег получит пользователь при продаже части акций. И когда вам станет известна эта сумма (скажем, 500 долларов США), следующий SQL-запрос обновит сумму депозита (например, для президента Скруба):

UPDATE users SET cash = cash + 500 WHERE id = 9

Конечно, если база данных или веб-сервер упадут в промежутке между этими DELETE и UPDATE, президент Скруб может потерять все свои деньги. Вам не нужно беспокоиться о таких случаях! Также возможно, из-за многопоточности, какой-нибудь умник может «обмануть» ваш сайт, заставив его выплатить ему сумму более одного раза. Пока что об этом можно не беспокоиться, но если вас эта возможность всё-таки волнует, вы можете использовать SQL-транзакции (с таблицами InnoDB). См. Http://dev.mysql.com/doc/refman/5.5/en/sql-syntax-transactions.html если интересно.

Для простоты можно потребовать у пользователей продавать все акции одновременно, или ни одной, а не несколько.

И ещё: обязательно проверьте ваш код, зайдя в систему от имени другого пользователя и продав что-нибудь. Вы всегда можете «купить» эти акции вручную с помощью phpMyAdmin.

Как всегда, убедитесь, что ваш HTML-код валиден!

buy

Теперь внедрим процесс покупки акций с помощью контроллера, скажем, buy.php и нескольких шаблонов(view). (Как и прежде, не стоит беспокоиться о нарушении сервиса или состояние гонки).

Интерфейс пользователя остается на ваше усмотрение, но, как всегда, можете обратиться к https://finance.cs50.net/ за вдохновением и подсказками.

Убедитесь, что пользователь не потратил больше денег, чем у него есть на руках. И также удостоверьтесь, что пользователи могут покупать только целые акции, а не их части. Для выполнения этого требования важно знать, что вызов наподобие такого:

preg_match("/^\d+$/", $_POST["shares"])

вернет true тогда и только тогда, когда $ _POST [ «shares»] содержит неотрицательное целое число благодаря использованию регулярных выражений. Подробнее смотрите на http://www.php.net/preg_match.

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

Когда очередь дойдет до хранения символов акций в базе данных, позаботьтесь о том, чтобы они были в верхнем регистре (это такое соглашение), независимо от того, как они были введены пользователем. То есть «free» и «FREE» нужно обрабатывать как одни и те же акции. Однако не заставляйте пользователей вводить символы только в верхнем регистре.

Если вы реализуете таблицу для портфолио пользователей так, как мы (с этим же составленным первичным ключом), тогда SQL-запрос вроде того, что ниже, вставит новую строку в таблицу, если указанной пары id и symbol не существует в каком-то строке. А если существует — количество акций в нем будет увеличена (например, на 10):

INSERT INTO portfolios (user_id, symbol, shares) VALUES(9, 'FREE', 10) ON DUPLICATE KEY UPDATE shares = shares + VALUES(shares)
history

Итак, ваши пользователи уже могут покупать и продавать акции и даже пересматривать свое портфолио. Только вот истории своих операций они пока не видят.

Улучшите свою реализацию покупку и продажи, чтобы она вела логи операций, запоминая для каждого следующую информацию:

  • Были ли акции проданы или куплены;
  • символ этих акций;
  • количество проданных или купленных акций;
  • цена акции на время операции;
  • дата и время операции.

После этого с помощью контроллера, скажем, history.php и нескольких шаблонов, дайте возможность пользователям просматривать историю операций.

index

Взгляните на index.php и добавьте ссылку на buy.php, history.php, logout.php, quote.php и sell.php (или их эквиваленты), если их там еще нет, чтобы можно было перейти туда с портфолио за один клик!

Дополнительная функциональность

Наконец, вишенка на торт! Остался только один пункт, на ваш выбор. Реализуйте хотя бы одну функциональность из списка ниже. Выбор за вами! Только убедитесь, что ваш HTML-код корректен.

  • Дайте возможность пользователям, уже залогинились, менять пароль.
  • Присылайте пользователям «сообщение» на электронную почту каждый раз, когда они покупают или продают акции.
  • Дайте возможность пользователям класть дополнительные деньги на счет.