Archive for the ‘Web’ Category

Нормализация базы данных

Попала в руки одна замечательная книжка — PHP 6 and MySQL 5 for Dynamic Web Sites , за авторством Larry Ulman. В целом, книга расчитана на новичков — середнячков, но затрагиваются и довольно серьёзные вещи, при чем объясняется весьма доходчивым языком.

Задела глава про нормальные формы. Довольно мудрёную тему автор раскрывает в весьма доходчивой манере. На русском издания я не нашел, поэтому перевел эту часть книги. Обьем статьи довольно большой, поэтому я разобью на несколько постов. В этом будет вводная часть.

Вкратце, что такое нормализация. Большинство современных субд разработаны на основе реляционной алгебры, которая появилась раньше самих реляционных субд под авторством некого доктора Кодда. Он же вывел несколько правил, или форм, по упорядочиванию данных и их отношений. Всего таких форм 6 + две вне конкурса, Бойса-Кодда и доменно-ключевая.

На практике редко нормализуют дальше 3-ей нормальной формы. Поподробнее узнать обо всех нормальных формах и теории по ссылкам внизу поста. Read On…

Вложенные SQL запросы: одновременное добавление одинарных и множественных строк в INSERT

Понадобилось для одной задачи сделать выборку из таблицы А по определенным условиям и вставить ее в таблицу B.
Можно особо не заморачиваться, сделать SELECT  в переменную, потом пройтись циклом по INSERT и все.

Но это не очень красивое решение. Очень запросов много + на переменные память расходуется. Проще использовать вложенные запросы, они же nested queries. Но тут возникает проблемка —  если вставлять  только данные из INSERT, то все ок, но если добавлять еще и какие-нибудь неизменные значения, то выдается Subquery returned more than 1 value.

Чтобы не обьяснять на пальцах — сама таблица B

CREATE TEMPORARY TABLE `B` (
  `status` TINYINT(4) NULL DEFAULT NULL ,
  `pair` TEXT NULL DEFAULT NULL ,
)
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci;

Копируем из таблицы A и вставляем в B так

INSERT INTO `B` (`pair`) 
            SELECT `pair` FROM `A` ORDER BY RAND() LIMIT 100;

Если поле status имеет неизменное значение, например 123, то просто вот так уже не вставить

INSERT INTO `B` (`status`, `pair`) VALUES (
            (SELECT `pair` FROM `A` ORDER BY RAND() LIMIT 100),
            123);

Не совсем красивое решение нашел

UPDATE `B` SET `status` = '123' WHERE 1=1;

Но хотелось бы в одну строку все заделать. Отпишитесь плиз в коментах, кто знает, как реализовать.

Проблемы с сериализацией массивов

Иногда бывает надо сохранить массив с данными куда-то, чтобы потом его достать и прочитать. Есть две хорошие функции для этого — serialize и unserialize. И все бы ничего, но вот иногда массив не получается десериализовать назад — возвращается предательское bool(false) вместо заветного массивчика.

Проблем может быть две — первая это включенная директива magic_quotes. Лечится примерно так —

(get_magic_quotes_gpc()) ? stripslashes(unserialize($variable)) : unserialize($variable);

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

Второй вариант, с которым втух я на несколько часов, это переносы строки. Если массив имеет в себе ячейку, в которой несколько строк, то он нифига не сериализуется взад, а выдаст false. Лечится убиванием \r\n

$str = preg_replace("/\n/", "", $str);
$str = preg_replace("/\r/", "", $str);

Класс для работы с сервисами сокращения ссылок

Небольшой класс, возвращает сокращенную ссылку. Можно обращаться как напрямую к конкретному сервису, так и выбранному рандомно через Shorteners->any(). Есть возможность указать прокси.

Работает с

  1. bit.ly
  2. goo.gl
  3. is.gd
  4. tinyurl.com
  5. dlmn.org
  6. ndurl.com
  7. qr.cx
  8. gatorurl.com
  9. jmb.tw
  10. linkee.com
  11. mtny.mobi
  12. cli.gs
<?php

error_reporting(1);

class Shorteners {

    public $proxy = null;

    public function bitly($url) {
        $connectURL = 'http://api.bit.ly/v3/shorten?login=masstest&apiKey=R_44403eb439622dd59ba2598255f30824&uri=' . urlencode($url) . '&format=txt';
        return $this->curl_get_result($connectURL, NULL);
    }

    public function googl($url) {
        $connectURL = 'http://goo.gl/api/shorten';
        $post_fields = "security_token=null&url=" . urlencode($url);
        $res = $this->curl_get_result($connectURL, $post_fields);
        $obj = json_decode($res);
        $ans = (!empty($res)) ? $obj->{'short_url'} : false;
        return $ans;
    }

    public function isgd($url) {
        $connectURL = 'http://is.gd/api.php?longurl=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function tinyurl($url) {
        $connectURL = 'http://tinyurl.com/api-create.php?url=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function dlmn($url) {
        $connectURL = 'http://dlmn.org/submit/?url=' . urlencode($url) . '&ajax=false';
        $page = $this->curl_get_result($connectURL, NULL);
        preg_match("/<input id=\"dlmn-loc\" value=\"(.*?)\"/si", $page, $out);
        if (strlen($out[1]) < 3) {
            return false;
        } else {
            $answer = (empty($out[1])) ? false : "http://" . $out[1];
            return $answer;
        }
    }

    public function crum($url) {
        $connectURL = 'http://crum.bs/api.php?function=simpleshorten&url=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function ndurl($url) {
        $connectURL = 'http://www.ndurl.com/api.generate/?url=' . urlencode($url) . '&type=web';
        $res = $this->curl_get_result($connectURL, NULL);
        $obj = json_decode(stripslashes($res));
        $obj = $obj->data;
        $ans = (!empty($res)) ? $obj->{'shortURL'} : false;
        return $ans;
    }

    public function qr($url) {
        $connectURL = 'http://qr.cx/api/?longurl=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function gatorurl($url) {
        $connectURL = 'http://gatorurl.com/api/rest.php?url=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function jmb($url) {
        $connectURL = 'http://jmb.tw/api/create/?newurl=' . urlencode($url);
        return $this->curl_get_result($connectURL, NULL);
    }

    public function linkee($url) {
        $connectURL = 'http://api.linkee.com/1.0/shorten?input=' . urlencode($url);
        $res = $this->curl_get_result($connectURL, NULL);
        $obj = json_decode($res);
        $ans = (!empty($res)) ? $obj->{'result'} : false;
        return $ans;
    }

    public function mtny($url) {
        $connectURL = 'http://mtny.mobi/api/?url=' . urlencode($url) . '&ismobile=false&type=simple';
        return $this->curl_get_result($connectURL, NULL);
    }

    public function cligs($url) {
        $connectURL = 'http://cli.gs/cligs/new';
        $post = "URL=" . urlencode($url);
        $page = $this->curl_get_result($connectURL, $post);
        preg_match("/Your new clig has been created/si", $page, $out);
        if (strlen($out[0]) < 3) {
            return false;
        } else {
            preg_match("#http://twitter.com/home\?status=(.*?)\"#si", $page, $out);
            $answer = (empty($out[1])) ? false : $out[1];
            return $answer;
        }
    }

    public function any($url) {
        $func = get_class_methods($this);
        $sh = $func[array_rand($func, 1)];
        return $this->$sh($url, NULL);
    }

    private function curl_get_result($url, $postdata) {
        $ch = curl_init();
        $timeout = 5;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        if (isset($postdata)) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
        }
        if ($this->proxy) {
            //var_dump( $this->proxy );
            curl_setopt($ch, CURLOPT_PROXY, $this->proxy);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, "60");
            curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
            curl_setopt($ch, CURLOPT_TIMEOUT, "60");
        } else {
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, "30");
            curl_setopt($ch, CURLOPT_TIMEOUT, "30");
        }
        $data = curl_exec($ch);
        curl_close($ch);

        return trim($data);
    }

}

$test = new Shorteners();
$url = "http://rambler.ru";
$class_methods = get_class_methods('Shorteners');
foreach ($class_methods as $method):
    echo $test->$method . "\r\n";
endforeach;
?>

Простой способ проверки процесса на существование

Для простых многопоточных систем очень часто используют запуск в фоновом режиме.  Запускают их примерно вот так:

php script.php &

И управление процессом от юзера уходит, отловить такой процесс из другого скрипта уже не получится. А если процесс выполняет какую то одну и ту же задачу с интервалом по крону, и нужно, чтобы при запуска нового процесса старый был убит? Поймать его можно аццкой смесью bash + php:

$killPid = exec("ps ux | awk '/script_name.php/ && !/awk/ {print $2}'");//находим пид процесса
      if($killPid)//если есть, киляем
	  exec ("kill ".$killPid);
exec('php script_name.php &');//запускаем новый

Бесплатный планировщик от IBM developerWorks

Как-то давно проскальзывал линк на один технический материал по PHP, расположенный на сайте IBM. Еще тогда удивила полезность информации с данного ресурса. Про статью я забыл, но пару дней назад понадобился один специализированный софт, который есть у голубого гиганта. Пришлось там создавать аккаунт, и я ни разу не пожалел, что сделал это. Read On…

Примеры использования strace для отладки скриптов

Strace — это встроенный перехватчик системных вызовов и сигналов в линуксе. Основная его цель — это перехват информации относительно какого-то процесса. Крайне незаменимая штука при отладке скриптов, особенно закодированных.

Пример использования — есть один сервис, который я написал, и обьявилась там проблема — каким-то неведомым образом права на файл, куда пишется лог, начали выставляться криво. А именно: вместо положенных rw — rw — rw стали w — wx — wT. Проблема тут в том, что надо указывать права при смене не как:

chmod('file.txt', '0666'); или
chmod('file.txt', 666);

в этом случае 0666 будет string, а вот так:

chmod('file.txt', intval('666', 8));

Так они уже будут в корректной форме.

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

strace php ./script.php 2>&1 | grep file.txt

Ответ будет вида:

open("/path/to/file.txt", O_RDWR|O_CREAT|O_APPEND|O_LARGEFILE, 0666) = 4
chmod("/path/to/file.txt", 0666) = 0

Вот так можно отмониторить все обращения к файлу
Read On…

Размножение строк — функция генерации всех возможных значений из шаблона {}

Понадобилась такая функция, решил размять мозги. Функция понимает в качестве разделителя запятые, |,  ;. Вызов показан в конце.

<?php

function mashUp($str){
preg_match_all("/{(.*?)}/", $str, $out);
$headArr = $out[1];

for($i = 0; $i < sizeof($headArr); $i++)
{
	$valArr = preg_split("/(\||,|;|	)/", $headArr[$i]);
	($i == 0)? $sTotal = count($valArr) : $sTotal = $sTotal * count($valArr);
	$varr[] = $valArr;
}
for($i = 0; $i < $sTotal; $i++){
	$strings[$i] = $str;
}
foreach($headArr as $num => $val):
	$currPos = 0;	//позиция ключа в $varr
		for($i = 0; $i < $sTotal; $i++)
		{
			if($i == 0):
					if($num == 0)
					{
						$sChange = ( $sTotal / ( count($varr[$num]) ) );
					}
						else
					{
						($old)? $sChange = $old : $sChange = $sChange;
 						$sChange = ($sChange / (count($varr[$num])));
					}

					if(count($varr[$num]) < 2){
						$old = $sChange;
						$sChange = $sTotal;
						}
					if($num == (count($headArr) - 1))
					{
						$sChange = 1;
					}
					$sNum = $sChange;
			endif;

			if($sNum == 0){
					$sNum = $sChange - 1;
					$currPos++;
				} else {
					$sNum--;
				//$currPos++;
				}

			if($currPos > (count($varr[$num]) - 1))
			$currPos = 0;
			preg_match("/{(.*?)}/", $strings[$i], $res, PREG_OFFSET_CAPTURE, 0);
			$strings[$i] = preg_replace("!".preg_quote($res[0][0])."!si", $varr[$num][$currPos], $strings[$i]);
		}
endforeach;
return($strings);
}
$str = "some {1a|2a,3a;4a	5a} strings {1b|2b|3b} that {1c} need to {1d|2d} post {test|example|primer}";
echo maashUp($str);
?>

MYSQL RAND(), как получить рандомную строку из БД

Чем дольше занимаюсь кодингом, тем чаще втыкаюсь в такие вещи, которые сто раз уже использовал, а на 101 сломал голову.

Есть таблица, в ней N тысяч записей, нужно вытащить одну рандомно, кой-чего с ней сделать, и если все ок то ее использовать, если нет то вернуть на место с соответствующей отметкой и взять другую, пока не будет найдена подходящая по условиям.
Я, особо не мудря, делаю цикл while до условия, пока не найдена подходящая запись. В цикле такой запрос к БД

SELECT * FROM `table` WHERE 1 ORDER BY RAND() LIMIT 1

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

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

//запрос макс количества записей в переменную
SELECT FLOOR(RAND() * COUNT(*)) AS `offset` FROM `table`

//далее запрос с этой переменной
SELECT * FROM `table` LIMIT $somevar, 1

Он же по тестам получился самый быстрый.

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

SELECT * FROM `table` WHERE id >= (SELECT FLOOR( MAX( id ) * RAND( ) ) FROM `proxy` ) ORDER BY id LIMIT 1

Все варианты расписаны тут.

Единственное, что не устроило во втором варианте, это два запроса к базе.

Как люди зарабатывают на своих блогах.

Наткнулся в ридере на вот такую статейку: State Of The Blogosphere 2010 или Какие Дела в Блогосфере 2010 по-русски если.  И там приводится интересный график: как зарабатывает большинство на своих блогах.
Так вот, написаны в данной статейке интересные факты, например, какие страны наиболее продвинуты в блоггинге. Понятно, что Северная Америка всех рвет, но вот от Южной я не ожидал такой прыти 🙂

В статье большое число аналитики типа какие типы постов (текст, фото, видео) вы используете, какой у вас блог (корпоративный, для души, профессиональный), платят ли блоггеры за пост или нет, получают ли бабки, сколько трафика имеют разные типы блоггеров, используют ли аналитику или нет Для блоггеров будет наверно крайне познавательно, но лично меня вот заинтересовал данный график — Каким обазом вы зарабатываете на блоге. Оказывается, большинство блоггеров ведет блог просто для души, и никак его не монетизирует. Типа как я, пишут всякую полезную и не очень инфу 🙂 И таких — 53%. Остальные, сверху вниз:

  • публикуют рекламу всяких мероприятий
  • вешают всякие рич медиа рекламки.
  • получают что-то нахаляву за обзор этого чего-то
  • публикуют платные посты
  • продают гостевые посты
  • пиарят партнерки
  • вешают контекст, Бегун или Адсенс например.
  • баннеры, кнопки

Вот такие дела в мировой блогосфере 🙂

css.php