« Поставить закладку » « Сделать стартовой »

« Форумы » « Блоги » « Статьи » « Новости » « Файлы » « Realcoding IRC » « Site map » « Поиск »


Главная Главная
Анонсы Анонсы
Форумы Форумы
Каталог Каталог
Поиск Поиск
Опросы Опросы
Книжный магазин Книжный магазин
Реклама на сайте
Публикации Публикации
Партнеры Партнеры
Карта Карта сайта
Рассылки Рассылки
RSS экспорт
Настройки Настройки
О нас пишут О нас пишут
Контакты Контакты
Гостевая книга Гостевая книга


ПнВтСрЧтПтСбВс
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
    Популярное
Анализ статистики базы данных InterBase/FireBird

Новостной портал

GD Library - эффективное использование

Приложение 2. Сетевое приложение "Игра в доминирование".

Абстрактная фабрика. Abstract Factory.

Работа с WDDX в РНР

Безопасное программирование на языке Perl

Команды Windows

Типы

Основы методологии проектирования ИС. Жизненный цикл по ИС




    Архив файлов



    Сообщества

    Документация

    Кто на сайте
Вы не зарегистрированы.
Имя:

Пароль:

Запомнить

Регистрация позволит Вам пользоваться дополнительными сервисами.
Сейчас на сайте:
Гостей: 131
Пользователей: 0

Статьи:: Интернет технологии :: Perl :: Быстрое создание веб-приложений на Perl: вводная



отправить ссылку другу версия для печати  Обсудить на форуме

Быстрое создание веб-приложений на Perl: вводная

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



Хочется рассмотреть простую ситуацию, которая по моему мнению, часто имеет место быть при разработке малых и средних проектов. А ситуация такая: необходимо создать небольшой (средний) сайт, причем принимается решение отказаться от CMS, так как движок нужен небольшой, наворотов в админке не нужно, сложность примерно 16-24 человеко/часов. Для примера требуется небольшой сайт, который будет содержать статьи определенного типа (обычные текстовые статьи) и новости. Плюс небольшая админка для добавления статей и новостей. Условимся, что у нас есть "большая" разница между этими двумя типами контента в рамках этой статьи.

Проблема


В таких ситуациях довольно часто принимается решение написать свой велосипед, то есть движок. Рассмотрим именно такую ситуацию, на примере которой рассмотрим так же прелести Perl и CPAN.
Полноценную реализацию MVC не предлагаю, это слишком много для нашего маленького проекта. Для Perl написан вагон и маленькая тележка фреймворков (как MVC, так и не очень), например отличный Catalyst, который очень и очень похож на RubyOnRails (или наоборот, я не в курсе хронологии). Так же есть множество поменьше, для любопытствующих стоит взглянуть сюда.

Мы же для простоты реализуем похожий механизм, но попроще. Итак, посмотрим на составляющие нашего движка (LAMP — это as default) в виде модулей:
1. Данные — DBIx::Class
2. Отображение — Template Toolkit
3. Управление — своими руками
Небольшое отступление. Я давно не люблю папку cgi-bin и всячески стараюсь ее избегать, почти на всех хостингах (а тем более дома) разрешены файлы .htaccess. Создаем такой файл в корневой папке проекта и записываем туда:
Options +ExecCGI
AddHandler cgi-script pl
DirectoryIndex index.pl

Теперь можно исполнять скрипты с расширением .pl прям в текущей директории. Кроме того страницей по умолчанию будет наш скрипт index.pl.
Далее советую всегда создавать конфиг. Вариаций множество, каждому нравится по разному, у меня минимально это выглядит так:

package Conf;
use warnings;
use strict;

BEGIN
{
    use Exporter;
    our (@ISA, @EXPORT);
    @ISA = qw(Exporter);
    @EXPORT = qw(
        $DB_Host $DB_Port $DB_Name $DB_User $DB_Pass
    );
}

our $DB_Host = "host";
our $DB_Port = 3306;
our $DB_Name = "our_db";
our $DB_User = "our_table";
our $DB_Pass = "our_password";
1;


Ничего военного, сюда вносятся глобальные параметры проекта и экспортируются.

Данные


Структура БД


Вернемся к движку. Пункт первый — это работа с данными, за нее у нас будет отвечать пакет DBIx::Class, который включает несколько модулей. Для начала создадим простую БД, с которой мы будем работать. Не стоит критично относится к структуре базы, она максимально простая, так же синтаксис без всего лишнего, минимизируем затраты.

create table users (
    id smallint not null primary key auto_increment,
    name varchar(32) not null,
    pass varchar(32) not null);

create table categories (
    id int not null primary key auto_increment,
    name varchar(128) not null) charset cp1251;

create table articles (
    id int not null primary key auto_increment,
    category_id int not null,
    title varchar(255) not null,
    content text not null,
    author varchar(128) not null comment 'Author of article',
    added_at timestamp not null,
    added_by smallint not null comment 'Admin user ID') charset cp1251;

create table news (
    id int not null primary key auto_increment,
    added_at timestamp not null,
    title varchar(255) not null,
    content text not null,
    is_put_on_main bool not null default 0 comment 'Show on main page?',
    added_by smallint not null) charset cp1251;

Таблица пользователей содержит самые основные данные (учет посещений и прочих нас пока не волнует), таблица categories содержит разделы статей, например "автомото", "спорт", "кулинария" и т.п., таблица articles содержит собственно статьи, а таблица news - новости. В последней есть поле is_put_on_main, которое отвечает за показ новости на главной. Так же почти в каждой таблице я задаю кодировку — это привычка, кто уверен — не делайте.

Отображение в код


Хорошо, таблицы у нас есть, теперь необходимо отобразить их в коде. Модуль DBIx::Class позволяет полностью отойти от написания SQL-кода и общаться с таблицами, как с объектами. Работать с этим модулем можно двумя способами: либо вручную описывать структуры каждой таблицы, либо воспользоваться автоматикой. Рассмотрим оба способа по порядку.

Ручной метод


Смотрим в код, далее будут пояснения. Создадим в корне нашего проекта папку с именем DB и в ней создадим четыре файла: User.pm, Category.pm, Article.pm, News.pm, вот содержимое этих файлов.

# file User.pm
package DB::User;

use base qw/DBIx::Class/;

__PACKAGE__->load_components(qw/PK::Auto Core/);
__PACKAGE__->table('users');
__PACKAGE__->add_columns(qw/id name pass/);
__PACKAGE__->set_primary_key('id');

__PACKAGE__->has_many('articles' => 'DB::Article',
    { 'foreign.added_by' => 'self.id' });
__PACKAGE__->has_many('news' => 'DB::News',
    { 'foreign.added_by' => 'self.id' });

1;

# file Category.pm
package DB::Category;

use base qw/DBIx::Class/;

__PACKAGE__->load_components(qw/PK::Auto Core/);
__PACKAGE__->table('categories');
__PACKAGE__->add_columns(qw/id name/);
__PACKAGE__->set_primary_key('id');


__PACKAGE__->has_many('articles' => 'DB::Article',
    { 'foreign.category_id' => 'self.id' });

1;

# file Article.pm
package DB::Article;

use base qw/DBIx::Class/;

__PACKAGE__->load_components(qw/InflateColumn::DateTime PK::Auto Core/);
__PACKAGE__->table('articles');
__PACKAGE__->add_columns(qw/id category_id title content added_by author/);
__PACKAGE__->add_columns('added_at' => { data_type => 'timestamp' });
__PACKAGE__->set_primary_key('id');


__PACKAGE__->belongs_to('category' => 'DB::Category',
    { 'foreign.id' => 'self.category_id' });
__PACKAGE__->belongs_to('user' => 'DB::User',
    { 'foreign.id' => 'self.added_by' });

1;

# file News.pm
package DB::News;

use base qw/DBIx::Class/;

__PACKAGE__->load_components(qw/InflateColumn::DateTime PK::Auto Core/);
__PACKAGE__->table('news');
__PACKAGE__->add_columns(qw/id title content is_put_on_main added_by/);
__PACKAGE__->add_columns('added_at' => { data_type => 'timestamp' });
__PACKAGE__->set_primary_key('id');


__PACKAGE__->belongs_to('user' => 'DB::User',
    { 'foreign.id' => 'self.added_by' });

1;

Итак, небольшие пояснения. Имеем четыре очень похожих файла, сначала объявляем базовым модуль DBIx::Class, далее используя механизм __PACKAGE__ вызываем его методы, а именно: load_components - загружаем компоненты для нашего модуля (PK::Auto для работы с автоинкрементированными primary_key, Core — основной набор для работы со связями, строками и столбцами). Далее указываем таблицу, после чего добавляем названия столбцов. Для работы со столбцами таких типов, как datetime, date и timestamp используется небольшой модуль InflateColumn::DateTime. С помощью него поля указанных типов можно использовать в программе, как объекты типа DateTime, со всеми вытекающими удобствами. После чего указываем primary key (если он составной, то указываем несколько полей set_primary_key(qw/name1 name2/);.
Далее находятся знакомые для знающих RubyOnRails методы has_many(), belongs_to() и другие. Эти методы предназначены для создания связей между таблицами.
Документация по чудному модулю DBIx::Class, где все подробно описано, включая туториал и cookbook.

Теперь нам нужно использовать сие чудо, для этого нам нужен модуль DBIx::Class::Shema, который является абстракцией схемы данных. В корневой папке проекта создаем файл с именем, идентичным имени папки с классами, описывающими таблицы, в нашем случае это будет DB.pm Вот как он выглядит у меня.

package SDB;

use base qw/DBIx::Class::Schema/;
use Conf;

__PACKAGE__->load_classes();

sub GetSchema()
{
    my $dsn = "dbi:mysql:$DB_Name:$DB_Host";
    my $sch = __PACKAGE__->connect($dsn, $DB_User, $DB_Pass);
    
    return $sch;
}

1;

В целом, использовать DBIx::Class::Schema можно и без функции GetShema(), метод load_classes() автоматически загружает все файлы, найденные в одноименной папке. Я дописал небольшую функцию, что бы удобнее было получать схему. Без этой функции соединение в коде выглядело бы так:

my $dsn = "dbi:mysql:$DB_Name:$DB_Host";
my $sch = DB->connect($dsn, $DB_User, $DB_Pass);

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

Автоматический метод


В "ручном" примере мы вручную задавали все связи между таблицами. Существует модуль DBIx::Class::Shema::Loader, который выполняет загрузку и создание классов автоматически. Для этого необходимо добавить в структуру БД описание внешних ключей (foreign keys). Используя их загрузчик автоматически создаст необходимые связи. Вот как это выглядит:

package DB;
use base qw/DBIx::Class::Schema::Loader/;

__PACKAGE__->loader_options(
    inflect_singular => 1,
    components => qw/InflateColumn::DateTime/
);

1;

# Использование

use DB;
my $sch = DB->connect( $dsn, $user, $password, $attrs);

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

Использование схемы


Теперь посмотрим, как это все вместе используется непосредственно в коде.

use DB;

my $sch = DB->GetShema();
# Поиск пользователя по id
my $user = $sch->resultset('User')->find({ id => $id });

# Добавление новости
my $new_id = $sch->resultset('Category')->populate(
[
    [qw/title content is_put_on_main added_by/],
    [$ntitle, $ncontent, 0, $user_id]
]);

# Удаление статьи
$sch->resultset('Article')->find({ id => $aid })->delete;

Далее, покажем наши данные.

Отображение


Я использую систему шаблонов Template Toolkit. Есть еще несколько систем, например Mason, но так исторически сложилось, что мой выбор пал на Template Toolkit.
Template Toolkit — это система обработки шаблонов. Посмотрим ее использование сразу на примере. Для начала создадим в корне проекта папку tmpl и в ней создадим папку site. В папке tmpl/site создадим файл site следующего содержания:

<html>
<head><title>Portal</title></head>
</body>
<div id="top"></div>
<div id="content">
[% PROCESS $content %]
</div>
<div id="bottom"></div>
</body>
</html>

Далее, сделаем там же файл start_page:

News and articles

Вот такой простой файл с одной строчкой. Это будет заготовка нашей стартовой страницы. Свяжем все вместе и получим примерно такой код нашего скрипта index.pl:

#!/usr/bin/perl -w

use strict;

use CGI;
use Template;

use Conf;
use DB;

# инициализируем CGI
my $q = CGI->new;
my %p = $q->Vars;

# ...шаблоны
my $tmpl = Template->new(
{
    INCLUDE_PATH => 'tmpl/site',
    INTERPOLATE => 1,
    EVAL_PERL => 1
}) || die "$Template::ERRORn";

# ...данные
my $sch = DB->GetShema();

# теперь мы готовы к работе
my $tmpl_vars = {};
$tmpl_vars->{content} = 'start_page';

print $q->header(-type => 'text/html', -charset => 'windows-1251');
$tmpl->process('site', $tmpl_vars) || die $tmpl->error(), "n";

Две строчки про CGI думаю всем понятны, далее идет создание объекта Template, главным параметром которого является INCLUDE_PATH - пусть к шаблонам. Чуть ниже мы создаем схему данных и соединяемся с базой. Далее мы создаем хэш, в который будем складывать все переменные, которые необходимо передать в шаблон. В нашем случае мы передаем только одну переменную content, эта переменная используется в директиве PROCESS в шаблоне site. Еще ниже мы запускаем обработку шаблона и указываем стартовый шаблон - site, а так же передаем хэш переменных.

В шаблоне site используется директива PROCESS, она запускает вложенную обработку другого шаблона, имя которого передано параметром, но так, как у нас имя хранится в переменной, то мы указываем это непосредственно - [% PROCESS $content %]. Таким образом в тело шаблона site вставится содержимое шаблона start_page. Добавим немного разнообразия. На главной странице мы должны отображать статьи и новости, но не все, а, скажем, последние десять. К тому же новости только те, которые помечены соответствующим флагом в таблице. Перед обработкой шаблона добавим в наш скрипт несколько строк:

my $articles = [$sch->resultset('Article')->search(undef,
{
    order_by => 'added_at desc',
    rows => 10,
    page => 1
})];
my $news = [$sch->resultset('News')->search(
{
    is_put_on_main => 1
},
{
    order_by => 'added_at desc',
    rows => 10,
    page => 1
})];
$tmpl_vars->{articles} = $articles;
$tmpl_vars->{news} = $news;

Следует заметить, что мы использовали [] для создания спискового контекста, иначе в скалярном контексте функция search() возвращает объект типа ResultSet, а нам нужен именно массив данных.

Итак, подробно описывать не имеет смысла, так как все довольно явственно. Единственное, это использование параметров rows/page. Они необходимы для создания так называемых pager-ов, с помощью которых удобно организовывать постраничный вывод, а так же применяются для простого отбора записей, что является частным случаем. Так же кол-во статей и новостей можно вынести в конфиг.

Далее, изменим шаблон start_page:

<h1>Новости</h1>
[% FOREACH n = news %]
<div class="short_note">
    <span class="title"><a href="?">
    [% n.added_at.dmy('.') %] [% n.title %]
    </a></span>
    <span class="content">[% n.content FILTER html %]</span>
</div>
[% END %]
<h1>Статьи</h1>
[% FOREACH a = articles %]
<div class="short_note">
    <span class="title"><a href="?">
    [% a.added_at.dmy('.') %] [% a.title %]
    </a></span>
    <span class="sub"><a href="?">
    Раздел: [% a.category.name %]</a></span>
    <span class="content">[% a.content FILTER html %]</span>
</div>
[% END %]

Отмечу использование поля added_at, как объекта. Для него вызывается метод dmy(), который форматирует дату в формат ДД-ММ-ГГГГ с переданным разделителем, в нашем случае точка. Объект DateTime поддерживает локали и корректно отображает дату в зависимости от текущей (или выбранной) локали. Так же он содержит множество методов для форматирования и работы с датами.

Я пока намеренно не добавлял валидные ссылки, сделаю это позже.
В целом мы видим два похожих блока, которые стоит вынести в отельный файл. Создадим файл short_note в папке tmpl/site:

<div class="short_note">
    [% text = node.content;
    IF text.length > 512;
        text = text.substr(0, 512);
    END %]
    <span class="title"><a href="?">
    [% note.added_at.dmy('.') %] [% note.title %]
    </a></span>
    [% IF note.category %]
    <span class="sub"><a href="?">
    Раздел: [% note.category.name %]</a></span>
    [% END %]
    <span class="content">[% text FILTER html %]</span>
</div>

Теперь наш шаблон start_page примет такой вид:

<h1>Новости</h1>
[% FOREACH n = news %]
[% PROCESS short_note note = n %]
[% END %]
<h1>Статьи</h1>
[% FOREACH a = articles %]
[% PROCESS short_note note = a %]
[% END %]

Теперь мы вызываем обработку шаблона short_note и передаем ему в качестве параметра note текущую новость или статью.

В шаблоне выполняется проверка на наличие поля category, что у нас будет признаком статьи, в этом случае мы выводим название раздела.

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

Управление


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

my $act = $p{'a'} || 'start';

if ($act eq 'start')
{
}
elsif ($act eq 'article')
{
}
elsif ($act eq 'news')
{
}
# ....
else
{
}

Итак, каждую ссылку в скрипте будет сопровождать параметр a - action. Он будет задавать текущий контекст. Таким образом, ссылки выше в шаблонах можно сменить на такие:

<a href="?a=article&id=[% note.id %]">
<a href="?a=category&id=[% note.category.id %]">

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

$p{'id'} =~ s/D//g if ($p{'id'});

То есть, если у нас есть какой-то номер, будь то статьи, новости или каталога, то мы вырежем оттуда все не-цифры. Простой и дубовый метод.

Рассмотрим далее примеры кода для контекстов.

if ($act eq 'start')
{
    $tmpl_vars->{content} = 'start_page';
    my $articles = [$sch->resultset('Article')->search(undef,
    {
        order_by => 'added_at desc',
        rows => 10,
        page => 1
    })];
    my $news = [$sch->resultset('News')->search(
    {
        is_put_on_main => 1
    },
    {
        order_by => 'added_at desc',
        rows => 10,
        page => 1
    })];
    $tmpl_vars->{articles} = $articles;
    $tmpl_vars->{news} = $news;
}
elsif ($act eq 'article')
{
    $tmpl_vars->{content} = 'full_article';
    $tmpl_vars->{article} = $sch->resultset('Article')->find({ id => $p{'id'} });
}
elsif ($act eq 'category')
{
    $tmpl_vars->{content} = 'category';
    $tmpl_vars->{category} = $sch->resultset('Category')->find({ id => $p{'id'} });
}
elsif ($act eq 'news')
{
    $tmpl_vars->{content} = 'full_article';
    $tmpl_vars->{article} = $sch->resultset('News')->find({ id => $p{'id'} });
}
else
{
    # см. ниже
}

Поиск я не рассматривал, он довольно прост, из формы мы передаем введенные данные для поиска среди статей и новостей и выводим результаты. Так же стоит помнить, что в Perl существуют удобные модули для проверки данных на валидность, переданных из форм, например HTML-CheckArgs или HTML-QuickCheck. Существую более продвинутые инструменты, например HTML-Widget или HTML-Tag. Это полноценные системы для создания виджетов и проверки данных на валидность. Очень удобны в коде, а так же удобны для повторного использования. Единожды созданный виджет можно использовать во множестве приложений.

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

    print $q->header(-location => '?a=start');
    exit;

Это не умно и не круто, зато безболезненно. Для тех, кто беспокоится о лишнем запросе к серверу, можно сделать следующим образом (до обработки контекста):

my %action = (
    'start' => 'Main page',
    'news' => 'News page',
    'article' => 'Full article',
    # ....
);
my $act = ( $p{'act'} && defined( $actions{$p{'act'}} )) ? $p{'act'} : 'start';

То есть, если задан контекст и он присутствует в списке — использовать его, иначе установить 'start'. Хэш контекстов используется вместо массива для облегчения проверки - defined(...).

Администрирование


Для администрирования необходимо создать некий инструмент. С точки зрения модели построения админка ничем не отличается от вышеприведенной системы, кроме авторизации и нюансов с текстом. Рекомендутеся создать отдельную папку для шаблонов, например tmpl/admin.
Для авторизации я использую два инструмента: Digest::SHA1 и CGI::Session. Первый обеспечивает шифрование, второй — сессии.
Итак, рассмотрим на простом примере применение этих инструментов. Пример намеренно упрощен до безобразия.

Шаблоны:

[%# Шаблон login %]
[% IF err %]
<span class="error">Wrong login</span>
[% END %]
<form action="" method="post">
<input type="hidden" name="a" value="login"/>
Login: <input type="text" name="login"/>
Password: <input type="password" name="login"/>
<input type="submit" value="Login"/>
</form>


В скрипте админки нужно дописать вход и выход из системы, а так же сессии:

use CGI::Session;
use Digest::SHA1 qw(sha1_hex);

# ... после CGI загружаем сессию
my $s = CGI::Session->load(undef, undef, { Directory => 'ssss' } );

# ... после определения контекста
if ($s->empty && $act !~ /login(_form)?|logout/)
{
    print $q->header(-location => '?a=login_form');
    exit;
}
else
{
    my $user = $sch->resultset('User')->find({ id => $s->param('uid') });
    $tmpl_vars->{user} = $user;
}

if ($act eq 'login_form')
{
    $tmpl_vars->{content} = 'login_form';
}
elsif ($act eq 'login')
{
    unless (my $u = &login($p{'login'}, $p{'pass'}))
    {
        $tmpl_vars->{content} = 'login';
        $tmpl_vars->{err} = 1;
    }
    else
    {
        $s = $s->new;
        $s->param('uid', $u->id);
        
        print $s->header(-location => '?a=start');
        exit;
    }
}
elsif ($act eq 'logout')
{
    $s->delete;
    print $q->header(-location => '?a=login');
    exit;
}

# и небольшая функция
sub login
{
    my ($u, $p) = @_;
    
    my $pp = sha1_hex($p);
    my $res = $sch->resultset('User')->search({
        name => $u,
        pass => $pp
    });
    
    my $user = $res->next;
    return $user;
}


Пример сильно дубовый, но тем не менее, он показывает суть.
Модуль CGI::Session поддерживает хранение сессий как в файле, так и в БД. Так же необходимо указать срок истечения — expired. В примере использовано хранение в файлах в каталоге ssss.
Модуль Digest::SHA1 - как альтернатива MD5.

Следующий нюанс касается создания форм для ввода данных. Во-первых, необходимо создать так называемые CRUD-методы (CReate, Update, Delete). Для этого, например, существует модуль DBIx::Class::WebForm. Так же по запросу CRUD на CPAN можно найти еще несколько подобных модулей.
Во-вторых, необходимо организовать удобный ввод текста статей и новостей. Лично я использую FCKeditor, хотя есть множество других. Такие редакторы довольно просто интегрируются в страничку и дают пользователям удобство и счастье в жизни.
В-третьих, стоит позаботится о валидации данных из форм. Например, модуль DBIx::Class::Validation проверяет данные перед отсылкой в базу, так же есть всевозможные валидаторы данных из форм, которые работают совместно с виджетами или формами, например CGI::FormBuilder, CGI::QuickForm и т.д. По запросу "Form", "Validate" или "Widget" можно найти множество модулей для этих целей.

Заключение


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

Вот архив примера, а здесь - рабочий пример. На создание примера ушло меньше часа.

-NOT_FOR_HOLYWARS-

Автор: http://khizhaster.habrahabr.ru/



Рубрика: Perl




Вышел MySQL 5.1.30, первый стабильный рели....

MySQL

После публикации 29 тестовых версий анонсирован первый стабильный релиз MySQL 5.1, пригодный для промышленной эксплуатации и обеспечивающий увеличение производительности для "тяжелых" SQL запросов, по сравнению с MySQL 5.0, примерно на 15-20%. Главные новшества появившиеся в MySQL 5.1:


Подробнее... | Рубрика: MySQL | Добавлено: 28.11.2008

Тестирование параллельных программ.

Тестирование

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


Подробнее... | Рубрика: Тестирование | Добавлено: 28.11.2008

Архитектура AMD64 (EM64T).

Архитектура AMD

Аннотация. В статье кратко рассматривается архитектура AMD64 компании AMD и ее реализация EM64T компании Intel. Описаны особенности архитектуры, ее возможности, достоинства и недостатки.


Подробнее... | Рубрика: Архитектура AMD | Добавлено: 27.11.2008

Остальные статьи:

Платформа 2009. Определяя будущее
Windows Vista Bridge Sample Library - упра...
Оптимизация 64-битных программ
Подгрузка через AJAX HTML-кода, содержащег...
Обзор нового релиза самой мощной Ajax библ...
Firebug 1.3 и 1.4 alpha — что нового и инт...
Релиз Microsoft Silverlight 2.0. Что новог...
XML документация в C#
Курсоры в MySQL 5
Microsoft опубликовала подробности о сесси...
Microsoft делится подробностями о том, что...
Тестируем новый javascript от нового брауз...
MySQL Query Cache
Использование провайдеров компиляции в As...
Чего мы ждем от C# 4.0
Delphi 2009 и C++Builder 2009
Джоэл Спольски и Джеф Этвуд запустили новы...
Поиск кода Google /* что нового? */
10 jQuery скриптов для улучшения интерфейс...
Генераторы отчетов FastReport 4 и QuickRep...


Цитата дня (все,добавить):

Портал фрилансеров

работа на дому


    Рубрикатор

Программирование

C/С++
Обучение
Windows API
XAML
Моделирование
Паттерны
Visual Basic 7 .NET
WxWidgets
Функции WinApi
Функции С++
Разработка под Mac OS
Eiffel
Visual Studio 2008
UI дизайн
Алгоритмы
Конкурсные статьи
Turbo Pascal
Visual Studio
CASE-средства
Visual Studio 2005
Без VCL
Delphi
Тех. документация
Тестирование
Software Testing
ООП
TCP/IP
Google Android
Windows Installer
.NET Framework
Драйвера
C# C Sharp
Справка
Проектирование
Информ. системы
Visual Basic
Assembler
Оптимизация кода
Gtk+
Компоненты
Реинжиниринг
Управление проектами
Extreeme programming
Lotus Notes
Алгебраическое проектирование


Интернет технологии

PHP
Perl
ASP
WAP
Cookies
SSI
CGI
Web Servers
VB Script
DNS
CSS
XML
Html
Java Script
Java2ME
Firewall
Flash
.htaccess
Apache
VRML
Протоколы
Поисковые системы
Технология JAVA
Учебник по PHP
Учебник по JavaScript
Учебник по XML
Java Q&A
AJAX
DHTML
XHTML
Dreamweaver
Web 2.0
Python
Вебмастеру
Cisco
Ruby on Rails
Silverlight

Базы данных

Access
InterBase
MySQL
Oracle
ADO .NET
Основы SQL
Учебник по Access 2002
MS
Microsoft FoxPro
Доступ к данным
XML в MS SQL Server 2000
ODBC и MyODBC
Обучение
Caché
DB2
PostgresSQL
Sybase
Теория
Хранилища данных
Безопасность
Реляционные данные
MySQL и mSQL

Остальное:

Разное
Обзоры книг
Безопасность
Графика и дизайн
Юмор
Linux
Фракталы
Microsoft Axapta
Многоядерность
Сети
Microsoft Office
Работа
MS-DOS
Криптография
Графика и игроделание
Новости SDK
Системы защиты
Учебник по AutoCad
CVS
Windows XP
Windows Server 2003
Windows Vista
Windows 7
Мероприятия