Loading...
brslv avatar brslv 69 Точки

Някои разяснения относно проекта по Web Development Basics.

Здравейте.

Тези дни захванах проекта по WDB. Първо, поздравления за заданието - изглежда интересно и провокира към задълбаване в практическия процес на изграждане на реално-работещ апликейшън. Изникнаха обаче някои въпроси, които отнасям към всеки, който може да помогне, но най-вече към автора/авторите.

Ще се постарая да съм кратък:

1. Към момента имам работещ рутинг енджин с възможност за дефиниране на къстъм рутове, опционални параметри, регекси и други гъзарийки. Искам да потвърдите, обаче, дали правилно разбирам точка 1 от задължителните функционалности - Default routing system. От нас се иска проложението да работи по подразбиране по схемата controller/action/parameter_1/parameter_2? Ако (по някакъв начин при стартирането на апп-а) юзъра (developer-а) избере къстъм рутове - дефолтния рутинг механизъм да се изключи? Предполагам това е идеята, но ще бъда благодарен, ако потвърдите.

2. Areas. Прочетох едно-две неща, но отново ми е малко абстрактно. Доколкото разбирам се изисква да се даде възмоност да се дефинират групи рутове. Не съм използвал такова нещо в ASP, а доколкото видях там има такова понятие, затова ще помоля за кратко разяснение. Правя аналогия с групирането на рутове в Laravel, което съм използвал, но може и да се бъркам.

3. Strongly typed views. Отново нещо от света на ASP, доколкото успях да се информирам. И пак моля за два-три реда идеи какво представлява това чудо...

И едно последно питане - не видях да е забранено използването на библиотеки от packagist/composer. Предполагам е пропуск в условието, но все пак...

Това е засега,

мерси! :)

3
PHP Web Development Basics
RoYaL avatar RoYaL Trainer 6849 Точки

Здравей,

Супер тема. Ще се възползвам освен да ти отговоря и да направя едно цялостно пояснение за фреймуърка, който се изисква, така че се надявам да стане ясно за всички, които евентуално имат въпроси :)

1. Иска се раутинг система по подразбиране, която в общия случай е controller/action/parameter1/parameter2

2. Иска се някакъв лесен начин, дивелъпърът да може да конфигурира различна раутинг система. Би било хубаво дефолтната да остане в сила, освен ако той изрично не е казал да се използва единствено и само неговата. Т.е. каква е идеята:

- Ако имаме два контролера - Users и Topics и двата контролера миат метод add() и имаме конфигурация, която казва, че като извикаш с POST usertopics/addnewtopic/ ще се извика TopicsController->add(), то трябва когато потребителят достъпи:

    2.1. users/add - да извика UserController->add();

    2.2 topics/add - да не се извика нищо, евентуално да даде грешка, че такъв раут не съществува  

    2.3. usertopics/addnewtopic - да извика TopicsController->add()

- т.е. кастъм раутовете са с по-голям приоритет от тези по подразбиране

3. Areas са нещо като модули, които си имат собствени контролери. Ако например правиш комплексно приложение и то си има в него форум (както например системата на СофтУни) - то форумът е една Area. представяме си го като една папка Areas в нея папка Forum и в нея контролери, модели и вю-та. В такъв случай site.com/forum/topics/read/6 ще извика от Froum area-та, TopicsController->read(6)

3.1. Иска се да има лесен начин да се дефинират къстъм раутове за area-та, които отново имат по-висок приоритет от раутовете по подразбиране. Конфигът на една Area не може да пипа в конфига на глобалния сайт или на другиа Area

4. Strongly typed views са вю-та, които можеш да извикаш само и единствено с определен тип модел. Нека си представим, че имаш View-то views/users/edit.php и да речем, че за да деноутваш, че определено вю приема само определен модел, то най-отгоре във вю-то си сложил анотация за модел променливата

/** @var \MyFramework\Models\User $model */

От тук даже IDE-то занапред в страницата, когато извикаш $model-> ще дава подсказки за методите от този клас

Когато, да речем, от контролера UsersController, метода edit() искаш да върнеш View и да му подадеш информация, си длъжен да подадеш обект от този тип \MyFramework\Models\User и ако не го направиш, трябва апп-а да гръмне когато се окаже, че си се опитал да достъпиш това вю, но на него не му е подадем обект от същия тип.

Вярно:

class UsersController
{
   public function edit($id)
   {
      $userData = $this->repo->getUsers()->findOneById($id);
      $user = new \MyFramework\Models\User($userData['name'], $userData['email']);
      return new View($user);
    }
}

Грешно:

class UsersController
{
   public function edit($id)
   {
      $userData = $this->repo->getUsers()->findOneById($id);
      $user = new \MyFramework\Services\TopicService($userData['name'], $userData['email']);
      return new View($user);
    }

5. Binding model-и - тук се иска да може определени методи, които ще се ползват за POST да приемат обект от определен тип и фреймуъркът като диспачва рикуеста, да хване какво има в POST масива и да напълни с него обект от съответния тип и да извика метода с вече напълнения обект. Ако има несъотвествия в POST масива със сигнатурата на байндинг модел обекта - да мята грешка.

Например

class UsersController
{
   public function edit(UserBindingModel $user)
   {
       $this->repo->getUsers()
           ->findOneById($user->getId())
           ->setName($user->getName())
           ->setEmail($user->getEmail())
       ->save();
   }
}

Горе долу става ясно, че този UserBindingModel явно има $id, $name, $email. В такъв случай, ако в $_POST няма ключовете id, name, email то нещо не е наред. Ако ги има - трябва да създаде този обект и да извика edit() с него

6. ViewHelper-ите са някакво консистентно API с което лесно може да се рендира HTML, използвайки РНР., например:

FormViewHelper::init()
    ->initTextBox()
         ->setName('username')
         ->setValue('pesho')
         ->setAttribute('class', 'field-green')
         ->create()
    ->initPasswordBox()
         ->setName('password')
         ->setAttribute('id', 'input-pass')
         ->create()
    ->render();

7. Анотации - това са коментари (doc blocks), които могат да се слага преди декларацията на някой клас/метод. Има езервирани тагове/анотации от PHPDoc като @param, @return и т.н. Тук се иска да си измислим наши си и даправим някакви действия, когато над някой метод съществува такава анотация. Анотациите могат да се четат с рифлекшън и тук идеята е, за да се диспачне рикуеста да се сканират контролерите и техните методи и да се види дали някой не притежава анотация, която да отговаря на текущия рикуест. Например дали над някой метод не пише

/**
* @POST
* @Route("user/profile")
* @param UserBindingModel $user
* @return View
*/
public function edit(UserBindingModel $user)
{

Което значи, че ако е дошъл рикуест user/profile през POST-а, трябва да се сканира методите и да видят дали някой няма точно това, което го има по-горе и ако го имат - да извика него.

8. Dependency Injection - тук се иска в някакъв конфигурационен файл да се опишат зависимостите на обектите и когато фреймуъркът трябва да вдигне даде обект (контролер) да провер в конфигурацията дали той не зависи от някакви обекти и съответно да ги инстанцира тях и да му ги подаде.

Например

class UsersController
{
     /**
     * @var \MyFramework\Data\IRepository
     */
     private $repo;

    public function edit(UserBindingModel $user)
    {
        $this->repo->getUsers()...

Ще резултира в Call to an undefined method getUsers() on a non-object ако $repo не е инициализирано. И тъй като не е инициализирано, ще трбябва фреймуъркът да види дали в конфигурацията някъде не е описана тази зависимост и да я резолне. Може обаче тази зависимост да зависи от други неща и ще трябва да резолвне и тях, за да я инстанцира нея. Например XML конфигурация:

<controller name="UsersController">
     <dependency name="repo" type="\MyFramework\Data\IRepository" mapTo="\MyFramework\Data\UsersRepository">
         <dependency name="db" type="\MyFramework\Adapters\IDatabaseAdapter" mapTo="\MyFramework\Adapters\MySQLAdapter" />
     </dependency>
</controller>

Тук трябва фреймуъркът да разпознае, че UsersController има зависимост с име 'repo' която е от определен тип, а нейният конкретен обект е UsersRepository. Обаче пък тази зависимост си има нейна зависимост с име на полето 'db', която е от определен тип, а обектът, който трябва да направи е MySQLAdapter. Трябва да се получи нещо като

$db = new MySQLAdapter();
$repo = new UsersRepository();
$repo->db = $db;
$usersController = new UsersController();
$usersController->repo = $repo;

$usersController->edit($user)

Разбира се през рифлекшън, тъй като например полетата $db и $repo в съответните класове бяха private.

10
brslv avatar brslv 69 Точки

Благодаря за обширните разяснения!

В тази връзка имам допълнителни питания:

1. В моя случай съм дефинирал container.php, което играе ролята на някакъв кофиг файл, в който дев-а може да си подготви някакви депендънсита. Да кажем, в този файл имам следното:

use App\Injector;
use App\InjectorTypes;

/**
 * Preparing some dependency injections.
 *
 */

Injector::prepare(
	InjectorTypes::TYPE_SINGLETON,
	'customRoutingProviderContract',
	'App\Providers\CustomRouting\CustomRoutingProvider',
	[
		'App\Providers\CustomRouting\DispatchingProvider',
	]);

Отдолу работи така: prepare приема типа обект, който искаме да инжектираме (където и да е), на кой интерфейс отговаря, кой е самия обект и какви депендънсита има.

Мисълта ми е - нямам xml-и, json-и, а просто php файл - много по-удобно ми е, пък и смятам, че е по-приятно за ползване. Ок ли е?

2. Доколко сме задължени да използваме анотации в докблока (изключвам задължителната част с раутването чрез анотации). Аз лично предпочитам да си дефинирам пропъртита и да ги обработвам с рефлекшън. Давам пример с моя IoC:

<?php

class BaseControllerProvider
{

    /**
    * Declares it's dependencies.
    */
    public $dependsOn = ['configProviderContract'];

}

В моя случай базовия контролер си извиква депендънси (configProviderContract) през пропърти, което се чете отзад с рефлекшън. В последствие мога да извиквам стойности от конфигурационния файл (използвайки инджектнатото депендънси) така:

<?php

namespace App\Controllers;

use App\Providers\Controller\BaseControllerProvider as BaseController;

class WelcomeController extends BaseController
{

	/**
	 * Says hi.
	 */
	public function index()
	{
		/**
		 * Get salute from the config file.
		 * In this imaginary case - Hi.
		 */
		$salute = $this->configProviderContract->get('salute');

		return View::make('welcome.index', compact('salute'));
	}

}

Може би защото не съм фен на анотациите, предпочитам да не ги използвам наляво надясно. Повече ми харесва подхода с рефлекшън на пропъртита. Ок ли е?

Засега е това, но в процеса на работа може да се наложи отново да вдигна темата с някой друг въпрос. :)

0
Kamigawa avatar Kamigawa 750 Точки

CSRF Token-a аналогично на Form Helper-a се очаква да го направим - т.е. класа държи правенето на тоукъна и неговата валидация?

примерно Token::init()->setToken(); - което си прави филда и дава стойност, и после някъде в контролера за въпросната заявка:

Token::init()->validate($token);

Или трябва да е нещо по-различно?

0
RoYaL avatar RoYaL Trainer 6849 Точки

Супер е. И според мен конфигурация с РНР е по-добре от конфигурация с json, xml, yaml, etc..Като цяло няма ограничение как точно ще го направите. Ако имаше стриктни ограничения всички фреймуърци щяха да са еднакви :) Дал съм такива примери, тъ йкато са най-близки до фреймуъците, с които евентуално повечето имат досег - ASP.NET/Spring.

Лично аз иначе не бих ползвал несъществуващи полета, които да създавам през __set/__get, заради факта че трудно би се усетил човек от къде идват, а и трябва да ги пише ръчно - няма дописване. Затова бих ги дефинирал като полета. Анотацията @var в случая за фреймуърка няма значение, той чете конфигурацията. Тази анотация е безобидна и предоставя само метаинформация на IDE-то (PHPStorm, Eclipse, Netbeans...), да дава интелисенс за от този обект нататък. А дори и да го направя така, бих ползвал анотацията @property в класа, която отново ще даде информация на ИДЕ-то какви пропъртита да очаква да бъдат създадени on the fly. Разбира се, не си длъже да го направиш така :)

1
SvetlinYotov avatar SvetlinYotov 7 Точки

Здравейте :)

Искам да попитам за вмъкването на Binding моделите в контролерите.

class TopicsController
{
    public function listTopics($someId, UserBindingModel $m ) //this is line 15
    {
    }
}

Горния код връща Catchable fatal error: Argument 2 passed to App\Areas\Forum\Controllers\TopicsController::listTopics() must be an instance of App\Bindings\UserBindingModel, null given inC:\xampp2\htdocs\project.com\app\areas\forum\controllers\TopicsController.php on line 15

В случая listTopics се вика при POST и самото байндване работи. Ако го махна "UserBindingModel $m" от параметрите и в самия метод добавя:

$g = new UserBindingModel();
echo $g->getName();

работи без никакъв проблем, ако приемем че в POST заявката имам ключ name.

Как може да се направи така, че да работи кода от първия snippet?

0
27/09/2015 04:07:04
RoYaL avatar RoYaL Trainer 6849 Точки

Покажи код как правиш мапването на POST заявката с байндинг модела

0
RoYaL avatar RoYaL Trainer 6849 Точки

https://github.com/svetlinyotov/SSFrameWork/blob/master/ssframe/FrontController.php

Тук където викаш action-а и разглеждаш параметрите като циклиш през тях, не проверяваш дали има binding model, който очаква и да го populate-неш.

Би трябвало да видиш съответния метод, който трябва да хитнеш дали не очаква нещо от тип BindingModel и ако да - да му извикш съответниь populate object метод и след това да подадеш на метода напълнения binding model

1
brslv avatar brslv 69 Точки

Аз отново имам питане, този път конкретно по CMS заданието. Захванах се да го мъча него, но ми звучи малко абстрактно като изисквания. Първо, става въпрос за ивентите:

  • The Editor can attach events to these elements (e.g. what to happen when form posts)
  • There have to be different predefined events (mail sending, db storage, comments (the posted form content could be later visible somewhere in the site)

@Royal, ще съм благодарен на едно две пояснителни изречения. Какво точно (или може би как се иска от нас) да имплементираме по тия две точки? Ще се радвам и колегите да се включат, ако имат идея или си представят как би могла да бъде реализирана идеята по тези точки. Трудно ми е да формулирам конкретен въпрос, защото ми бяга контекста/идеята на това...

Отделно за гридовете - отново - каква е идеята им, за какво ще се ползват и как си го представяте. На мен още ми е трудно да вникна конкретно в тези два детайла, а ми се иска, защото искам да дефинирам цялостното поведение на цмс-а, преди да го имплементирам.

Мерси отново и поздрави! :)

0
RoYaL avatar RoYaL Trainer 6849 Точки

В общия слчуай ивентите по-скоро са за формите и anchor-ите - какво би станало когато кликнеш събмит бутона в някоя форма или на определен линк.

Например првиш форма в която има 3 полета. Закачаш й mail event. Позволява ти да избереш кое поле е "to", кое е "subject" и кое е "body". Като го направиш, когато потребителят на фронтенда натисне събмит бутона се изпраща мейл до съответния to, със съответния subject и body.

За db storage си го представям като се кликне бутона да се сериализира в например JSON формат формата и да се записва в някаква таблица.

Comments е доста конкретен event - такъв който си има задължително body и автор например и може би връзка към друг comment. Чрез този ивент може editor-а да направи мини форум, блог и подобните апликейшъни, като създаде дървовидна структура от comment-и - един който е парент, други които са му деца и така например да симулира въпрос с отговори.

Как си го представям при бекенд юзъра(едитора) - при създаването на определена форма избира event. Като избере някой от predefined event-ите, го пита за допълнителни инструкции по event-а. Например ако избере mail, го кара да избере полето До, Тема и Текст. Ако избере event-а коментар - го кара да избере полето Тема, полето Текст и може би Автор (ако не се взима текущо логнатия юзър).

Това разбира се са моите представи. Всеки би могъл да реализира негова си идея върху това.

Идеята ми за гридовете е да има елемент грид, на който едитора избира контент от базата данни - такъв от тези форми, които са били db storage например. И гридът чете сериализирания json и го прави в табличен вид. Ключовете в джейсъна като имена на колони, и съответно стойностите попълнени надолу. Би било хубаво гридът да има странициране и филтриране по колони, но това далеч не е задължително.

1
brslv avatar brslv 69 Точки

Благодарско! Мисля, че се ориентирах!

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.