Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM

Autor wpisu: thejw23, dodany: 30.09.2009 19:02, tagi: framework, php

przeczytalem ostatnio wypowiedz MajareQ na temat frameworkow i juz sam wstep zrobil na mnie piorunujace wrazenie:"Jeśli się pytasz: “Który framework powinienem wybrać?” to popełniasz błąd logiczny. Zakładasz, że w ogóle potrzebujesz jakikolwiek framework."moim zdaniem nie. bledem logicznym jest zakladanie, ze piszac w php nie potrzebuje frameworka :)1) startna dzien dzisiejszy osoba szukajaca frameworka, ktora nie zadala sobie juz wczesniej pytan zadanych przez MajareQ, to na 99% jest osoba, ktora nigdy nie pisala na prawde duzej strony i na 99% w najblizszym czasie jej bedzie robila. wiec nie szuka frameworka do pisania klonu Twittera, czyli bardzo, bardzo wysoka wydajnosc nie jest dla mniej priorytetem. i tu dochodzimy do punktu drugiego2) pomijanie calkowicie roli w ktorej frameworki sprawdzaja sie najlepiejrola polegajaca na szybszym, latwiejszym i przyjemniejszym pisaniu kolejnych aplikacji. frameworki pozwalaja zaoszczedzic duzo czasu, odwalaja za nas brudna robote zwiazana z autoryzacja, formularzami, walidacji itd. jesli 99% (ok, niech bedzie 90%) wykorzystania frameworkow to nie sa strony potrzebujace porad z High Scalability, to dlaczego mam nie uzywac frameworka, aby lepiej mi sie pracowalo? dlaczego mam pisac wlasne rozwiazania od podstaw i samemu dokonywac zmian na miare wlasnej wiedzy, skoro moge siegnac po cos co jest rozwijane przez osoby co najmniej, a zazwyczaj lepiej, orientujace sie w php? dlaczego nie mam skorzystac z faktu, iz bledy same sie poprawia, nowe funkcje same sie dodadza - wszystko niemal 'magicznie', calkowicie bez mojej ingerencji, po prostu 'pop' i mam kolejna wersje frameworka do sciagniecia. i tak dochodzimy do punktu trzeciego3) praca i zabawaczym innym jest praca, zarabianie z pisania w php, a czym innym bawienie sie w php, pisanie glownie dla przyjemnosci, bez terminow (ew. z odleglymi), rodziny itd. takie pisanie, kiedy po kilku godzinach siedzenia czlowiek ma duza satysfakcje, ze cos zrobil minimalnie lepiej niz bylo wczesniej, czesto po prostu cieszac sie faktem, ze cos sie zrobilo, dziala i jest tak samo funkcjonalne jak inne gotowe rozwiazania dostepne na sieci - ale to jest moje, moje i tylko ja to zrobilem, hoooray :) niestety, czesto pozniej zaczyna sie praca plus rodzina i albo skorzystam z gotowego, sprawdzonego, stabilnego, dzialajacego poprawnie rozwiazania i bede mial gotowa rzecz w 30 minut, albo poswiece najblizsze 4-5h na to aby zrobic to samemu, majac w perspektywie fakt, iz strona nad, ktora pracuje na 90% nie trafi na liste 'optimized by High Scalability'. w 90% przypadkow nie potrzebuje napisac czegos co bedzie musialo miec wydajnosc Twittera, wiec nie bedzie dla mnie roznicy w jakim frameworku to napisze (a roznice w wydajnosci moga byc miedzy nimi duze). i tu dochodzimy do punktu czwartego4) framework jaki jest kazdy widzina pewno przed wybraniem frameworka trzeba wiedziec czego potrzebujemy, to nie ulega watpliwosci. problem w tym, ze dla 90% wykorzystania frameworkow, wszystkie liczace sie spelniaja te wymagania. maja wsparcie, sa rozwijane, maja obsluge wielu baz, maja mechanizmy wspierajace rozne silniki do cache`owania itd. dlatego wlasciwym kryterium jest to, w ktorym frameworku najlepiej mi sie pracuje. ktory dla mnie i tylko dla mnie jest najfajniejszy. dodatkowo jest jeszcze kolejne kryterium o ktorym warto pomyslec zanim zdecydujemy sie na jakies bardzo niszowe rozwiazanie. i tu dochodzimy do punktu piatego.5) w kupie razniejwlasne rozwiazania sa fajnie, ale jak jestem klientem i mam swoj sklep online napisany na czyims wlasnym silniku, to mam przesrane. tak po prostu. jak mi webmaster odejdzie, to zanim nowy sie polapie, to troche czasu minie - chyba kazdy kiedys robil cos w cudzym kodzie i wie jaka to jest 'przyjemnosc'. a jak wyglada sytuacja jesli mam framework? z nowym webmasterem nie ma problemu, po prostu szukam kogos kto zna dany framewok i sporo problemow z glowy. podobnie jesli mam firme z 20 ITmenami na wlasnym rozwiazaniu. przyjdzie nowa osoba, ile czasu bedzie musiala sie uczyc ? ile czasu bedzie sie uczyl nowy pracownik, ktory zna dany framework? a czas to pieniadz. analogicznie jesli szukam pracy, to znajde ja szybciej znajac gotowe rozwiazania czesto stosowane w firmach.6) uwagi koncoweprzewaga frameworkow nad domowymi rozwiazaniami jest zazwyczaj bezpieczenstwo, optymalizacja, 'magiczne' naprawianie bledow i dodawanie nowych funkcjonalnosci, trzymanie sie ogolnie przyjetych standardow, mniejsze lub wieksze wymuszanie pisania zgodnie z MVC. jak dla mnie to wystarczajaca lista, aby zarzucic wlasne rozwiazanie i zajac sie czyms gotowym, czyms co w miare szybko dostosuje do wlasnych potrzeb i bede czul sie 'jak u siebie'. mozna samemu napisac framework, tyko nadal pozostaje jeden minus - samemu trzeba wprowadzac wszelkie poprawki, a majac gotowe rozwiazanie czas na to przeznaczony moge poswiecic na poza komputerowe hobby. nie sama praca czlowiek zyje.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: batman, dodany: 29.09.2009 19:20, tagi: zend_framework

Zend Framework dostarcza programiście ogromną ilość narzędzi, które znacznie ułatwiają pracę nad aplikacjami internetowymi. Jednym z takich narzędzi jest Zend_Form. Jego wykorzystanie znacznie przyspiesza proces tworzenia formularzy wraz z walidacją i filtrowaniem wprowadzonych danych. Formularz można stworzyć na co najmniej kilka sposobów. W zasadzie każdy, kto używa ZF od dłuższego czasu,

Autor wpisu: batman, dodany: 29.09.2009 19:15, tagi: zend_framework

W poprzednim tekście poświęconym formularzom w Zend Framework-u, pokazałem jak w prosty i szybki sposób można stworzyć bezpieczny formularz, wykorzystując do tego Zend_Form. Dzisiaj pokażę w jaki sposób można stworzyć skomplikowany formularz, zawierający dwie kolumny z kontrolkami oraz podpowiedzi (tooltip).  By nie przesłonić głównego celu tego wpisu, nie będę dodawał filtrów i walidatorów do

Autor wpisu: Vokiel, dodany: 28.09.2009 10:56, tagi: php

Ostatnia część tworzenia projektu loggera zdarzeń – klasy Log. Skupimy się na wykończeniu projektu, który naprawi powstałe wcześniej niedociągnięcia, trudności w korzystaniu. Utworzona klasa połączy nam wszystkie funkcjonalności w jedno. Doda kilka funkcjonalności, wprowadzi wygodę w użytkowaniu, łatwość zmian. W pierwszej części ustaliliśmy wymagania klasy Log, ustaliliśmy strukturę, zasady funkcjonowania klasy oraz napisaliśmy interfejs. W drugiej części rozwinęliśmy nasz projekt o klasę logFile implementującą wcześniej utworzony interfejs, omówiliśmy możliwość rozszerzeń funkcjonalności o nowe klasy (LogDB) oraz spotkaliśmy się z problemem szybkiej zmiany rodzaju wykorzystywanej klasy.

Tworzenie klasy

Stwórzmy klasę, która połączy w sobie możliwość używania różnych klas loggera (logFile, logDb). Aby zachować ustalone na początku wymagania, dać możliwość łatwego wykorzystania, szybkiej zmiany silnika bez potrzeby wielu zmian w kodzie oraz maksymalnie skrócić zapis, musimy skorzystać z kilku technologi OOP.

Pierwsza z nich – klasa statyczna, druga – singleton. Czemu tak? Klasa statyczna + singleton załatwi nam szybkie i wygodnie użycie:

log::info('Szybko i sprawnie');

. Dzięki temu, po ustaleniu na początku skryptu silnika przechowywania logów w dalszej części możemy wygodnie korzystać z w/w zapisu. Dodatkowo przejście z logowania do pliku na logowanie do bazy nastąpi tylko w jednym miejscu. Jak zapewne zauważyliście nawet nazwa klasy została maksymalnie skrócona log, aby użycie było jak najbardziej wygodne.

Zatem do dzieła. Wzór klasy będzie następujący:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class log implements log_log{
	private static $driver;
	private static $drivers = array();
	private static $defaultDriverName = 'log_logFile';
 
	protected function __construct(){}
	protected function __clone(){}
 
	public static function instance(){}
	public static function addDriver(log_log $driver){}
	public static function useDriver($drivername=''){}
 
	public static function error($err,$driverName=''){}
	public static function warn($err,$driverName=''){}
	public static function info($err,$driverName=''){}
}
?>

Zmienne klasy

W klasie log mamy zadeklarowane 3 zmienne. Każda z nich ma swoje, łatwe do odgadnięcia, zastosowanie: private static $driver; – aktualny sterownik klasy loggera private static $drivers = array(); – tablica dostępnych (dodanych) sterowników private static $defaultDriverName = 'log_logFile'; – nazwa domyślnego sterownika

Statyczna zmienna klasy $driver; przechowuje aktualnie wybrany sterownik, do którego następuje zapis logów. W trakcie korzystania z klasy będzie możliwa zmiana sterownika, dzięki temu, nawet w jednym skrypcie zachowamy możliwość wybrania innego sterownika do zapisu logu konkretnego zdarzenia. Kolejna zmienna $drivers; jest tablicą przechowującą obiekty klas poszczególnych sterowników. Dzięki temu po jednokrotnym wykorzystaniu konkretnego sterownika, w przypadku jego ponownego użycia obiekt nie jest tworzony na nowo. Trzecia zmienna $defaultDriverName; jak sama nazwa wskazuje, przechowuje nazwę domyślnego sterownika klasy loggera. Dzięki temu klasa jest gotowa do użycia bez ustawiania sterownika przy każdym, pierwszym, uruchomieniu skryptu.

Metody

Trzech ostatnich metod error(), warn(), info() nie muszę omawiać, zostało to już przedstawione w poprzednich częściach. Jedyną różnicą względem poprzednich implementacji jest dodanie nowego parametru: $driverName =''. Parametr ten, jak nazwa wskazuje, podaje nazwę sterownika, którego należy użyć do zapisu danego komunikatu. Domyślnie nie jest zdefiniowany, a w przypadku jego nie podania, wykorzystywany jest domyślny sterownik. Istnieje jednak możliwość podania innego sterownika do zapisu konkretnego komunikatu. Dzięki temu rozszerzamy funkcjonalność naszej klasy, o wybór sterownika dla konkretnego komunikatu, lub pojedynczego wystąpienia.

Utworzyliśmy dwie metody z dostępem ustawionym na protected:

protected function __construct(){}
protected function __clone(){}

Ich zadaniem jest pilnowanie, aby nasza klasa była rzeczywiście singletonem. Zabezpiecza przed utworzeniem nowego obiektu klasy log z poza niej samej oraz przed utworzeniem kopii takiego obiektu.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Vokiel, dodany: 26.09.2009 16:56, tagi: php

W pierwszej części na temat optymalizacji bloga opartego na WordPress skupiliśmy się na kilku elementach: Zmianie memory_limit() poprzez php.ini, .htaccess, lub ini_set() w php. Następnie zapoznaliśmy się z częścią możliwości optymalizacji po stronie front-end’u (cache).

W dzisiejszej części będziemy kontynuować proces optymalizacji do tego stopnia, aby możliwym było bezproblemowe korzystanie z platformy blogowej. Zacznijmy od optymalizacji bazy danych.

Nieużywane szablony

Jeśli uruchamiając blog nie mogliśmy się zdecydować na konkretny szablon (theme), próbowaliśmy kliku z nich, do tego ustawialiśmy w nich jakieś opcje, dodatki, konfiguracje – to po zmianie na inny szablo wpisy dokonane dla poprzednich szablonów pozostaną zapisane w bazie. Jest to dobre rozwiązanie w przypadku, gdy zechcemy zmienić szablon (powrócić do wcześniej ustawionego) – konfiguracja zostaje przywrócona do etapu na jakim ją pozostawiliśmy. Jednak jeśli zdecydujemy się już na ten jedyny, wybrany szablon, zwykle nie będziemy go zmieniać, raczej upiększać, modyfikować. Możemy zatem śmiało usunąć pozostałe, oraz wszystkie wpisy w bazie danych które po nich pozostały. W tym celu logujemy się do menedżera MySQl (SQLyog, PMA) przeglądamy tabelę wp_options w poszukiwaniu wpisów odnoszących się do starych szablonów, wtyczek, które odinstalowaliśmy i usuwamy je.

Nieaktualne ustawienia wtyczek

W moim przypadku usunięcie nieaktualnych wpisów odnoszących do ustawień kilku szablonów oraz wpisów pozostałych po wtyczkach zmniejszyło ilość wierszy w tabeli wp_options o około 80 rekordów (wszystkie z ustawionym autoload na 'yes'). Dodatkowo postanowiłem zmienić opcję autoload dla wybranych wpisów (po uprzednim pełnym backupie bazy). Ustawienia zostały zmienione tylko dla tych wpisów, odnośnie których znaczenia byłem pewien, oraz tych, które nie miały wartości. Samo zużycie pamięci zostało nieznacznie zmienione – w granicach błędu statystycznego. Jednak ilość zwróconych wyników uległa lekkiemu zmniejszeniu oraz zmniejszył się czas trwania zapytań – zatem przyśpieszenie już jakieś nastąpiło.

Indeksy

Przeglądając wyniki z WP Tuner (włączone ustawienia: Show Everything) natknąłem się na kilka niepoprawnych zapytań, na zapytania z narzutem, bez odpowiednio ustawionych indeksów. Niezbędnym okazało się ponowne zalogowanie do MySQL’a celem naniesienia poprawek poprzez dodanie indeksów. Zwykle brak indeksów występuje w przypadku wtyczek.

Szkice, wpisy autosave

Jeśli zakończyliśmy edycję wpisów, oraz wszystkie szkice zostały opublikowane, bądź są nam już niepotrzebne możemy je usunąć. Upewnijmy się tylko uprzednio, czy opublikowane wpisy są już ostateczne i nie będziemy chcieli wracać do poprzednich wersji. Funkcjonalność WordPress’a jaką jest automatyczne zapisywanie szkiców bywa bardzo przydatna. Jednak po zakończonej edycji wpisu, opublikowania go w wersji, która nie wymaga zmian, pozostałe wersje wpisu są już niepotrzebne. Usuniemy je za pomocą zapytania sql:

DELETE FROM wp_posts WHERE post_type = "revision";

Cache

W pierwszej części wspomniałem o systemach cache dostępnych jako wtyczki, a także o możliwości napisania własnych. Poza cache generowanych plików warto ustawiać czas ważności cache takich elementów jak pliki js, css, grafiki, dla przeglądarki. Tak, aby w przypadku braku modyfikacji przeglądarka nie pobierała ponownie tych plików, nawet nie zgłaszała potrzeby ich pobrania.

Jedną z opcji będzie wydłużenie domyślnego czasu ważności plików w zależności od ich rozszerzenia. Do tego celu posłużą nam dyrektywy apache oraz plik .htaccess. Odnajdujemy plik .htaccess w lokalizacji wp-content/cache, edytujemy:

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
# BEGIN supercache
<IfModule mod_mime.c>
  <FilesMatch "\.html\.gz$">
    ForceType text/html
    FileETag None
  </FilesMatch>
  AddEncoding gzip .gz
  AddType text/html .gz
</IfModule>
<IfModule mod_deflate.c>
  SetEnvIfNoCase Request_URI \.gz$ no-gzip
</IfModule>
<IfModule mod_headers.c>
  Header set Cache-Control 'max-age=300, must-revalidate'
</IfModule>
<IfModule mod_expires.c>
  ExpiresActive On
ExpiresDefault A300
ExpiresByType application/x-javascript A3600
ExpiresByType text/css A3600
ExpiresByType image/gif A3600
ExpiresByType image/png A3600
ExpiresByType image/jpeg A3600
ExpiresByType text/plain A300
ExpiresByType application/x-shockwave-flash A3600
ExpiresByType video/x-flv A3600
ExpiresByType application/pdf A3600
ExpiresByType text/html A300
</IfModule>
# END supercache

Interesują nas wpisy od linii 16, czyli w momencie gdy moduł mod_expires.c jest dostępny. Wraz z nim dostajemy możliwość ustawienia domyślnego czasu ważności elementów strony w zależności od ich typu. Jak widzimy domyślnie ustawiony jest na A3600 – czyli 3600 sec = 1 godz. W przypadku, gdy grafiki nie będą się zbyt często zmieniać śmiało możemy wydłużyć ten okres do miesiąca czyli: A2592000. Zatem w liniach 19-23 możemy wpisać nowy czas aktualności plików dla przeglądarki. Poza określaniem czasu w sekundach mod_expires daje możliwość określania czasu za pomocą słownych określeń, np:

1
2
3
4
5
6
# Słowne określenia czasu ważności cache
<IfModule mod_expires.c>
  ExpiresActive On
ExpiresDefault "access plus 1 week"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType text/css "modification plus 5 hours"

Więcej szczegółów na stronie apache – module mod_expires

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Michał Środek, dodany: 26.09.2009 13:21, tagi: php

Z doświadczenia wiem, że programiści dzielą się na dwie grupy: tych co będą szukać optymalizacji na kazdym kroku oraz tych, którzy wolą dokupić dodatkową kość RAM i wyspać się zamiast pracować do późna w nocy ;) . Ja oczywiście należę do tej pierwszej. Już samo użycie php nie jest najoptymalniejszym rozwiązaniem, lecz od czasu do czasu postaram się opisać w kilku słowach jak przyspieszyć swoje skrypty. Zacznijmy od echo.

1
2
3
$cat = 'Mruczek';
echo "Mój kot ma na imię $cat i jest zawsze wesoły.";
echo 'Mój kot ma na imię '.$cat.' i jest zawsze wesoły';

Sposób działania jest prosty. Podczas użycia cudzysłowów ciąg znaków jest przeszukiwany w pod kątem posiadania jakichś zmiennych. Tutaj one istnieją więc skrypt podstawia pod $cat ciąg znaków Mruczek. Następnie wyrzuca na wyjście gotowy napis. W przypadku użycia apostrofów jest troszkę inaczej. PHP nie wyszukuje żadnych zmiennych ponieważ ma jasno określone miejsce w którym te zmienne się znajdują, co sprawia, że całość działa czasami nawet kilkukrotnie szybciej.

Czy dałoby się to jeszcze bardziej przyspieszyć? Oczywiście ;) . Wystarczy posłużyć się operatorem przecinka zamiast kropki.

4
echo 'Mój kot ma na imię ',$cat,' i jest zawsze wesoły';

Większość programistów o tym nie wie ale w przypadku operatora kropki, PHP najpierw tworzy wynikowy ciąg znaków co powoduje dodatkowe obciążenie pamięci. Przy dwóch kropkach tworzone są dwie nowe zmienne String(lub dwie tablice znaków — nie będę się zagłębiał w kod wewnętrzny PHP). Jeśli użyjemy przecinków, PHP po prostu będzie każdy z elementów interpretował jak kolejne parametry(choć pamiętajmy — echo nie jest funkcją!) i wyrzucał je bezpośrednio na wyjście. Pytanie: czy opłaca się buforować wyjście za pomocą kropki(np. w przypadku długiego czasu dostępu do wyjścia)? To już pozostawiam wam do przemyśleń. Poniżej testy prędkości przy użyciu Apache Benchmark:

Wersja z cudzysłowami: Time taken for tests: 100.027 seconds Complete requests: 32772 Failed requests: 0 Write errors: 0 Total transferred: 8206540669 bytes HTML transferred: 8201253107 bytes Requests per second: 327.63 [#/sec] (mean) Time per request: 305.219 [ms] (mean)

Wersja z apostrofami: Time taken for tests: 100.018 seconds Complete requests: 42646 Failed requests: 0 Write errors: 0 Total transferred: 10669918695 bytes HTML transferred: 10663047537 bytes Requests per second: 426.38 [#/sec] (mean) Time per request: 234.531 [ms] (mean)

Wersja z przecinkiem: Time taken for tests: 100.029 seconds Complete requests: 44111 Failed requests: 0

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: cojack, dodany: 26.09.2009 10:25, tagi: javascript

Dziś chciałbym Wam przedstawić pewną bardzo fajną i przydatną bibliotekę dla przeglądarki IE (której bardzo nie lubię Nie lubie IE), która pomoże nam zrobić z IE używalną i prawie web-dev friendly.

ie7.js – co to jest?

Biblioteka jest napisana przez Dean Edwards’a w całości w JavaScript (js), nie, nie jest oparta o framework jQuery. Co nam daje ta biblioteka? Poprawia wiele błędów, które uniemożliwiają nam w klarownym sposób tworzenie layoutów pod strony, nie dość że i tak są zgodne z w3c to mimo wszystko czasami zdarzy się że IE się wyburaczy, i mowa tutaj zarówno o IE6 jak i o IE7, chodź ta druga jest już trochę bardziej przystosowana do życia w rodzinie. Możemy też dzięki tej bibliotece cieszyć się przejrzystością plików *.png, dodając tym co chcemy obrazkom infix czyli blabla-trans.png, podkreślony jest infix, czyli to co chcemy dodać.

Naprawmy selektory css w ie

Biblioteka ie7.js naprawia nam takie selektory css jak:

  • rodzic > dziecko
  • rodzic + dziecko
  • rodzic ~ dziecko
  • .multiple.classes
  • :hover
  • :first-child
  • [attr]
  • [attr="value"]
  • [attr~="value"]
  • [attr|="value"]
  • [attr^="value"]
  • [attr$="value"]
  • [attr*="value"]

Naprawmy właściwości css w ie

Biblioteka ie7.js naprawia nam takie właściwości css jak:

  • background-attachment
  • background-image
  • bottom
  • cursor
  • display
  • font-size
  • margin
  • max-height
  • max-width
  • min-height
  • min-width
  • overflow
  • position
  • right

Naprawmy elementy html w ie

Biblioteka ie7.js naprawia nam takie elementy html jak:

  • abbr
  • img – mowa o infixie
  • label

Naprawmy pozostałe błędy w ie

Biblioteka ie7-squish.js naprawia nam takie elementy html jak:

  • Podwójny margin
  • Peekaboo
  • Nieprzewijana treść

Jest też jeszcze opis biblioteki ie8.js która poprawia braki w przeglądarce ie7, ale za dużo treści ma. Na samym dole artykuły wszystkie linki.

Jak używać?

Ażeby strona nam piknie śmigała robimy jedną rzecz gdzieś w nagłówku, pomiędzy atrybutami <head> a </head> wstawiamy o to ten kod:

<!--[if lt IE 8]>
<script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script>
<script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/ie7-squish.js" type="text/javascript"></script>
<![endif]-->

To wszystko, oczywiście możemy pobrać to na twardziela hostinga i trzymać, ale po co? ;]

Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.