CakePHP: JSON w widokach
Opublikowano 2011-08-27 11:31:13 CakePHP JavaScript
Pomysł jak i klasa do realizacji pomysłu są zaczerpnięte z tego wpisu. Jeżeli mamy akcję, której zadaniem jest zwrócić wynik w JSON; zazwyczaj nie ma potrzeby korzystania z plików widoków i layoutów. Niektórzy pokusiliby się o wyłączenie autorenderowania i następnie wpisali echo json_encode($something);.Bardziej doświadczeni programiście wiedzą, że jest to niezgodne z architekturą MVC. Do tego celu stworzona została klasa JsonView. Poniższy kod ląduje w pliku app/views/json.php.
class JsonView extends View {
var $content = null;
function __construct(&$controller, $register = true) {
if (is_object($controller) && isset($controller->viewVars['json'])) {
$this->content = $controller->viewVars['json'];
}
if ($register) {
ClassRegistry::addObject('view', $this);
}
Configure::write('debug', 0);
}
function render($action = null, $layout = null, $file = null) {
if ($this->content === null) {
$data = '';
} else {
$data = json_encode($this->content);
}
return $data;
}
}
Teraz w naszej akcji, której zadaniem jest wypisać obiekt JSON wystarczy wpisać:
function my_action() {
$this->view='json';
$json = array('myobject'=>true,'mvc'=>'not broken');
$this->set(compact($json));
}
W odpowiedzi akcji powinniśmy otrzymać:
{"myobject":true,"mvc":"not broken"}
Mechanizm bardzo prosty w implementacji, pozwalający znacząco przyspieszyć pisanie kodu z użyciem JSON.
CakePHP: Linki absolutne
Opublikowano 2011-08-26 21:46:20 CakePHP HtmlHelper
Ostatnio napotkałem problem tworzenia linków w mailu wysyłanym z sekcji administratorskiej aplikacji napisanej w CakePHP. Linki tworzyłem za pomocą metody link w HtmlHelper.
echo $this->Html->link('Link'
array(
'controller'=>'contents'
,'action'=>'view'
,'slug'=>'my-custom-slug'
)
);
Tak utworzony link tworzony jest z prefixem 'admin'; jakoże został utworzony z poziomu sekcji admin, która jest uzyskana za pomocą takiego prefixu. Pozbywamy się prefixu podająć w tablicy drugiego parametru element o kluczu takim jak nazwa prefixu, a w wartości prefixu FALSE. Zaleca się aby w każdym tworzonym linku anuluwać nieużywane, a zdefiniowane prefixy. Dla przypomnienia prefixy te zdefiniowane są w pliku app/config/routes.php.
echo $this->Html->link('Link'
array(
'controller'=>'contents'
,'action'=>'view'
,'slug'=>'my-custom-slug'
,'admin'=>FALSE
)
);
Teraz wystarczy tylko uzyskać link absolutny, czyli zaczynający się od http://tomaszmazur.eu/ zamiast standardowego /. W tablicy drugiego parametru tworzymy element o kluczu full_base i wartości TRUE.
echo $this->Html->link('Link'
array(
'controller'=>'contents'
,'action'=>'view'
,'slug'=>'my-custom-slug'
,'admin'=>FALSE
,'full_base'=>TRUE
)
);
CakePHP: Jeden silnik dla wielu aplikacji
Opublikowano 2011-08-22 01:02:28 CakePHP
Jak posiadać jedną kopię framework'a dla wielu aplikacji? Standardowe podejście to jeden silnik dla jednej aplikacji. Zakładając, że na serwerze posiadamy 10 aplikacji, w chwili gdy zechcemy zaktualizować silnik, musimy wgrać nową wersję 10 razy. Dodatkowo posiadamy 10 identycznych kopii źródeł CakePHP. Rozwiązaniem tego problemu jest posiadanie jednego silnika CakePHP wykorzystywanego przez wszystkie 10 aplikacji.
Na moim serwerze struktura katalogów wygląda następująco:
~/domains/domena1.pl/public_html/ ~/domains/domena2.com/public_html/
W każdym z tych katalogów posiadam 2 działające aplikacje CakePHP, zawartość tych katalogów to:
app/ cake/ plugins/ vendors/ .htaccess index.php
W momencie, gdy będę chciał zastosować strategię 'jeden silnik dla wielu aplikacji', zawartość folderu ograniczy się do:
app/ .htaccess index.php
Pozostałe foldery zostaną przeniesione do innej dowolnej lokalizacji. W moim przypadku przenoszę je do ~/cakephp_nr_wersji. Otrzymuję przykładowy efekt:
~/cakephp_1_3_10/cake ~/cakephp_1_3_10/plugins ~/cakephp_1_3_10/vendors/
Aby aplikacje wiedziały, gdy znajduje się silnik CakePHP, musimy dokonać modyfikacji pliku app/webroot/index.php
ustawiając stałą CAKE_CORE_INCLUDE_PATH na ścieżkę do katalogu zawierającego katalogi cake i vendors (u mnie jest to linia #53). W wyżej opisanym przykładzie wyglądałoby to następująco:
define('CAKE_CORE_INCLUDE_PATH', dirname(dirname(dirname(ROOT))).DS.'cakephp1_3_10');
Zmiany musimy oczywiście dokonać w każdej aplikacji z osobna, lecz jest to działanie tylko jednorazowe. Możemy pójść krok dalej i wyrzucić zawartość katalogu app bezpośrednio do public_html, ale o tym w oddzielnym wpisie.
CakePHP: HABTM - podstawy relacji hasAndBelongsToMany
Opublikowano 2010-12-26 22:01:55 HABTM CakePHP
1. Wprowadzenie
W artykule tym opiszę wszystkie istotne informacje dotyczące relacji HABTM(hasAndBelongsToMany). Na początku podam przykład takiej relacji wzięty z tej strony. Mamy artykuły i kategorie; artykuł może być przypisany do wielu kategorii, a kategoria może posiadać wiele artykułów. To jest istotą relacji HABTM. Aby utworzyć tą relację konieczne jest stworzenie w bazie danych, tabeli łączącej artykuły z kategoriami. Definicja w kodzie wygląda następująco:
<?php
//definicja modelu artykułów
class Article extends AppModel {
var $name = 'Article';
var $hasAndBelongsToMany = array('Category');//definicja relacji HABTM
}
<?php
//definicja modelu kategorii
class Category extends AppModel {
var $name = 'Category';
var $hasAndBelongsToMany = array('Article');//definicja relacji HABTM
}
W powyższym kodzie nie zdefiniowałem tabeli łączącej obydwa modele, jest spowodowane tym, że CakePHP domyślnie przyjmuje nazwę wziętą z konwencji; w tym wypadku articles_categories. Dodatkowo zostanie utworzony automodel ArticlesCategory, który umożliwi dostęp bezpośrednio do danych zawartych w tabeli łączącej. Należy jednak pamiętać o tym, że utworzony automodel nie jest powiązany ani z artykułem, ani z kategorią. Powiązanie takie można wywołać ręcznie:
//utworzenie relacji naszego automodelu do kategorii $this->Article->ArticlesCategory->bindModel(array(‘belongsTo’=>array(‘Category’)));
2. Modyfikacja relacji
Co jednak jeśli nazwa automodelu nam nie pasuje? Albo chcemy użyć innej nazwy tabeli łączącej? Nazwe te możemy dowolnie modyfikować, należy jednak pamiętać o tym aby zmiany wprowadzić w obu modelach. Wprowadzenie ich tylko w jednym modelu będzie skutkowało dużym bałaganem i niespodziewanym zachowaniem aplikacji.
<?php
//definicja modelu artykułów
class Article extends AppModel {
var $name = 'Article';
var $hasAndBelongsToMany = array(
'Category'=>array(
'with'=>'ArticlesCategories' //nowa nazwa automodelu
,'joinTable'=>'articles_categories_joined' //nowa nazwa tabeli łączącej
)
);//definicja relacji HABTM
}
<?php
//definicja modelu kategorii
class Category extends AppModel {
var $name = 'Category';
var $hasAndBelongsToMany = array(
'Article'=>array(
'with'=>'ArticlesCategories' //nowa nazwa automodelu
,'joinTable'=>'articles_categories_joined' //nowa nazwa tabeli łączącej
)
);//definicja relacji HABTM
}
Po zastosowaniu podanego wyżej kodu nasz automodel będzie się nazywał ArticlesCategories a tabela łącząca articles_categories_joined.
3. Analiza relacji
Wrócmy jednak do nazewnictwa proponowanego przez konwencję CakePHP. Jeśli chcemy wejść w szczegóły relacji HABTM, jest to nic innego niż 2 relacje hasMany z zastosowaniem modelu pośredniego. Podaję przykład jak utworzyć relację zachowującą się podobnie jak HABTM:
//definicja modelu artykułów
class Article extends AppModel {
var $name = 'Article';
var $hasMany = array('ArticleCategory');
}
//definicja modelu kategorii
class Category extends AppModel {
var $name = 'Category';
var $hasMany = array('ArticleCategory');
}
//definicja modelu łączącego
class ArticleCategory extends AppModel {
var $name = 'ArticleCategory';
var $belongsTo = array('Article','Category');
}
Jak widać wymaga to zdefiniowania dodatkowego modelu, pełniącego rolę łącznika. Nie polecam definiowania relacji w ten sposób, przykład ten został podany jedynie w celu zrozumienia zasady działania relacji HABTM.
4. Tworzenie i usuwanie relacji
Relacja HABTM jest dość specyficzną relacją. Aby przypisać artykuł o id=1 do kategorii o id=1 wywołujemy:
$this->Article->save(array(
'Article' => array(
'id' => 1,
),
'Category' => array(
'Category' => array(1),
),
));
Jeżeli chcemy przypisać nasz artykuł dodatkowo do kategorii 2 może skorzystać z kodu:
$this->Article->save(array(
'Article' => array(
'id' => 1,
),
'Category' => array(
'Category' => array(2),
),
));
Spowoduje to jednak usunięcie relacji z kategoria o id=1. Należy zatem pamietać, że przy modyfikacji relacji HABTM CakePHP najpierw kasuje wszystkie istniejące relacje. Poprawny kod wygląda następująco:
$this->Article->save(array(
'Article' => array(
'id' => 1,
),
'Category' => array(
'Category' => array(1,2),
),
));
Jak można się już domyślić, jeśli chcemy aby nasz artykuł nie należał do żadnej kategorii wywołujemy:
$this->Article->save(array(
'Article' => array(
'id' => 1,
),
'Category' => array(
'Category' => array(),
),
));
Aby zmienić to zachowanie można skorzystać z add-delete-habtm-behavior. Osobiście nie korzystałem z tego dodatku, ale w niektórych przypadkach może on okazać sie użytyczny.
5. Więcej informacji
W przyszłości planuję opisać sposób filtrowania danych objętych relacją HABTM.
CakePHP: Optymalizacja tabel ACL
Opublikowano 2010-11-02 11:55:11 CakePHP SQL Optymalizacja ACL
Natknąłem się w sieci na ciekawy kawałek kodu SQL, który drastycznie zwiększa wydajność zapytań do tabel ACL. Zmiana polega jedynie na nałożeniu indeksów i kluczy obcych. Kod został znaleziony na grupie dyskusyjnej CakePHP.
/* ACL Tables */ CREATE TABLE acos ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, parent_id INT DEFAULT NULL, model VARCHAR(255) DEFAULT '', foreign_key INT UNSIGNED DEFAULT NULL, alias VARCHAR(255) DEFAULT '', lft INT DEFAULT NULL, rght INT DEFAULT NULL ) ENGINE = INNODB; -- table name is quoted because it is a reserved word CREATE INDEX idx_acos_lft_rght ON `acos`(lft,rght); CREATE INDEX idx_acos_alias ON `acos`(alias); CREATE INDEX idx_acos_model_foreign_key ON `acos`(model(255),foreign_key); CREATE TABLE aros ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, parent_id INT DEFAULT NULL, model VARCHAR(255) DEFAULT '', foreign_key INT UNSIGNED DEFAULT NULL, alias VARCHAR(255) DEFAULT '', lft INT DEFAULT NULL, rght INT DEFAULT NULL ) ENGINE = INNODB; -- table name is quoted because it is a reserved word CREATE INDEX idx_aros_lft_rght ON `aros`(lft,rght); CREATE INDEX idx_aros_alias ON `aros`(alias); CREATE INDEX idx_aros_model_foreign_key ON `aros`(model(255),foreign_key); CREATE TABLE aros_acos ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, aro_id INT UNSIGNED NOT NULL, aco_id INT UNSIGNED NOT NULL, _create CHAR(2) NOT NULL DEFAULT 0, _read CHAR(2) NOT NULL DEFAULT 0, _update CHAR(2) NOT NULL DEFAULT 0, _delete CHAR(2) NOT NULL DEFAULT 0 ) ENGINE = INNODB; -- table names are quoted because they are reserved words CREATE UNIQUE INDEX idx_aros_acos_aro_id_aco_id ON `aros_acos`(aro_id, aco_id); ALTER TABLE aros_acos ADD CONSTRAINT FOREIGN KEY (aro_id) REFERENCES `aros`(id); ALTER TABLE aros_acos ADD CONSTRAINT FOREIGN KEY (aco_id) REFERENCES `acos`(id);