быстрое создание веб-приложений на 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 следующего содержания:
    Portal


    [% PROCESS $content %]





    Далее, сделаем там же файл 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::ERROR\n";

    # ...данные
    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:

    Новости


    [% FOREACH n = news %]
            [% n.added_at.dmy('.') %] [% n.title %]
        
        [% n.content FILTER html %]

    [% END %]

    Статьи


    [% FOREACH a = articles %]
            [% a.added_at.dmy('.') %] [% a.title %]
        
            Раздел: [% a.category.name %]
        [% a.content FILTER html %]

    [% END %]

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

    Я пока намеренно не добавлял валидные ссылки, сделаю это позже.
    В целом мы видим два похожих блока, которые стоит вынести в отельный файл. Создадим файл short_note в папке tmpl/site:
        [% text = node.content;
        IF text.length > 512;
            text = text.substr(0, 512);
        END %]
            [% note.added_at.dmy('.') %] [% note.title %]
        
        [% IF note.category %]
            Раздел: [% note.category.name %]
        [% END %]
        [% text FILTER html %]


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

    Новости


    [% FOREACH n = news %]
    [% PROCESS short_note note = n %]
    [% END %]

    Статьи


    [% 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. Он будет задавать текущий контекст. Таким образом, ссылки выше в шаблонах можно сменить на такие:

    Далее, нужно подумать о безопасности. Сейчас нас интересует только параметр 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 %]
    Wrong login
    [% END %]
    />
    Login: />
    Password: />
    />



    В скрипте админки нужно дописать вход и выход из системы, а так же сессии:
    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-
    Поделиться публикацией

    Комментарии 31

      0
      Хотя с Perl не знаком, но было интересно и поучительно читать, так сказать подглядеть: " а как там у них в другом мире ?". Напрашивается аналогия с Си (Perl) и C# (CMS, RoR...). Можно было-бы продложить и написать подобные заметки для других языков (c/php/phyton/ruby...).
        0
        Если я такое же напишу для рельсов то меня заминусуют и скажут баяяян! )
        В принципе могу дело 30 минут)
          +1
          для рельсов может и баян, а вот на чистом Ruby, было бы интересно
            0
            Ну здесь используется не чистый перл, а доп. модули, так что я бы не сказал, что рельсы - это из другой оперы.
            И это совсем не похоже на работу с C#, в частности с ASP.NET
              0
              Тут чутка не то сравнение, рельсы - это полновесный фреймворк, аналог его в перле - это Catalyst :)
              Кстати, с перлом можно работать аналогично ASP.NET, в той же манере
                0
                да, забыл добавить, что сейчас из "чистых" языков вспоминается только PHP, где в одном месте собрано почти всё. Perl же, можно сказать, исповедует другой путь, где сам по себе язык "почти" ничего не умеет, а все остальное реализовано модулями. Прямая аналогия - С/С++, в чистом виде на них ничего не сделать, в них даже ввод/вывод сделан библиотеками, и весь остальной функционал сделан так же. Я думаю, это потому, что язык изначально универсален и создавался для многих задач, в то время как PHP изначально ориентировался на WEB, потому разница в реализации. По большому счету, это выбор между набором инструментов и швейцарским ножом.
                  0
                  ну вот не надо :) Я еще помню времена, когда CGI параметры ручками собирались в перле. Каталист для перла это редкостное бууэээ, хотя может я просто не умею его готовить. На самом деле в перле очень сильно нехватает нормального ООП, из-за этого в нем приходиться реализовывать различные извраты. И это, пожалуйста киньте в меня ссылкой на тему того, как с перлом работать наподобии ASP.NET.
                    +1
                      0
                      опередили вы меня немного :-)
                      0
                      Сразу хочу оговориться, что всей базы (аналога System.Web) в Perl конечно же нет, но есть вот такие интересные вещи.
                      Apache2::ASP, вот домашняя станичка этого проекта.
                      Так же вот еще Win32::ASP, с этим я не сталкивался, но судя по описанию вполне подходит под определение "с перлом работать наподобии ASP.NET" :-)
                        0
                        Господа, пожалуй не стоит путать ASP и ASP.NET, это на самом деле очень разные платформы.
                          0
                          В этом частном случае, когда мы говорим о встраивании кода в тело страницы, можно считать их одинаковыми. Конечно, ни о каком CLR речи не идёт.
                            0
                            Нет, механизмы совсем другие. Я соглашусь с вами, что perl и asp похожи, но как человек, писавший 2 года не perl и работающий сейчас с asp.net, могу сказать вам, что принцип работы кардинально разный.
                            0
                            Само собой :) В целом, ASP.NET можно рассматривать как некий фреймворк, в котором созданы инструменты для работы с данными и библиотеки контролов.
                              0
                              именно, но наш разговор начался именно с упоминанием C#, который появился уже на .NET платформе, на простом ASP C# не используется.
                    0
                    на чистом руби под веб насколько мне известно не пишут. Есть просто другие фреймворки. Ждем mod_rubynius!
                      0
                      на чистом руби под веб редко кто пишет, потому что многие узнали о нем только в связке с рельсами. и некоторые просто не понимают, что можно писать под веб без фреймворка.
                    0
                    и правда, про рельсы писать не стоит, потому как уже написано много очень качественных материалов - популярная технология ;)
                • НЛО прилетело и опубликовало эту надпись здесь
                    +2
                    Топик написан хорошо, но, раз уж начали, необходимо логическое продолжение.
                    Да, вы показали, что перл может быть простым, но при этом не указали никаких его преимуществ. У вас получился код, неотличимый от аналогичного на руби/пхп/etc, никаких плюсов от использования перла тут нет. Если не продолжить, то читатель подумает — нафига мне тот же пхп, но с необходимостью инклюдить стандартные функции да еще и всякими мусорными операторами?
                      0
                      Не хотелось разводить холивары различными сравнениями с другими языками ;)
                      Согласен, что не предствил никаких преимуществ Perl, планирую сделать это дальше, насколько хватит моих познаний, но без прямых сравнений.
                      Все-таки я задумывал эти заметки, как обзорные, а не сравнительные, в противовес множеству статей про другие языки, тем более, что сейчас про Perl мало пишут. Кто-либо может заинтересоваться и нашего полку прибудет ;-)
                        0
                        нужно не доказывать кому-то, что именно эта технология спасет его от всех проблем. при всех преимуществах какого-то конкретного языка есть люди, которых даже с арматурой в руках не убедишь на него перейти. дело в привычках и личных убеждениях
                        0
                        если проект простой - то и хостинг дешевый. А не каждый поддерживает перл в дешевых пакетах...
                          0
                          перл есть почти везде, чего не скажешь о питоне и руби
                          0
                          Perl — когда-то я им увлекался, как-то по человечески жаль, что он уходит со сцены. Но после "другого" языка, смотреть на этот код как-то не получается.
                          Автору спасибо, было интересно.
                            +2
                            я бы не сказал что он совсем уже уходит со сцены. Есть большое количество популярных сайтов, написанных на Perl, и отказываться от него никто не собирается. Уже было тут что-то на эту тему. По-моему сейчас даже программистов на перле меньше, чем вакансий.
                              0
                              Есть такое дело - уже наверно полгода ищем толкового Perl программиста.
                              0
                              Спасибо, статья супер, почти все библиотеки из примера мне пришлось писать самому вручную, по этому было интересно прочитать, плюс сейчас, как раз, рассматриваю варианты применения стандартных библиотек для будущих проектов...

                              Есть пара вопросов:
                              - например тут:
                              my $news = [$sch->resultset('News')->search(
                              {
                              is_put_on_main => 1
                              },
                              )];
                              есть ли возможность задавать более сложный where

                              - и есть ли возможность при вставке задавать данные хэшом а не двумя массивами? или надо делать функцию-обертку?
                                0
                                1. можно, любые вариации.
                                2. $sch->resultset('Article')->populate(
                                [{
                                name => $p{'name'},
                                text => $p{'text'},
                                author => $p{'author'},
                                }]);
                                Каждый массив - это новая запись, их можно через запяту. Внутри каждый хэш - это набор полей.

                                В принципе, это все есть в документации на DBIx::Class, советую просмотреть хотя бы бегло, там довольно понятно, особенно Manual :)
                                  0
                                  Спасиб. В принципе уже читаю, просто для меня важна гибкость данного инструмента, потому и спросил сразу.
                                0
                                где вы были года 3 назад, когда я пытался научиться перлу и ничего не понимал?!

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.