Използване на Traits

dakata__92

Super Moderator
Колеги, как мислите? Използвам Traits за да въвеждам примерно от ядрото конфигурационен метод в даден клас или взимане на конекция с базата, вместо да наследявам навсякъде ядрото или ръчно да си пиша методите по контролерите. Правилно ли е това според вас?

PHP:
namespace System\Traits;

use \System\Core\AppKernel;

trait Database
{
	private function setDbConnection($object = null,$dbname = null)
	{
		AppKernel::getInstance()->setDbConnection($object,$dbname);
	}
	
	private function getDbConnection()
	{
		return AppKernel::getInstance()->getDbConnection();
	}
}

Примерно този път го ползвам някъде където ми е нужна конекция с базата. Имам дори сингълтон Trait. Просто според мен е по пригледно. Примерно описвам си над класът къде какво се ползва и вътре пък си описвам какво ползвам като трейтове:
PHP:
namespace System\Core;

use \System\Core\AppKernel;

/*Класът е Сингълтон.*/
class Controller extends AppKernel
{
	use \System\Traits\Singleton;
        use \System\Traits\Database;
}
Какво мислите за този начин на въвеждане на методи?
 
Ако ползваш DI в конкретния ти пример и двата trait-а ще са излишни. Можеш ли да дадеш малко по-сложен пример, в който trait-овете те улесняват?
 
anonimen каза:
Ако ползваш DI в конкретния ти пример и двата trait-а ще са излишни. Можеш ли да дадеш малко по-сложен пример, в който trait-овете те улесняват?

Идеята ми е вместо да се наследява ядрото и надолу по веригата всеки клас да има достъп до по - по - предходните методи тоест до тези на ядрото, съм решил да ползвам трейтове където ми се налага да използвам един метод. Примерно налага ми се в някой клас да ползвам нещо от конфигурационната папка използвам трейт.

Някъде съм си сбъркал логиката... :?
 
Разгледай този клас - https://r.je/dice.html
По време на самата конструкция на даден обект ти "връчва" това, което ти трябва. Например:

PHP:
class Controller {
private $_db, $_cfg;
__construct (Database $db, Config $cfg) { // тук си слагаш каквото ти трябва и автоматично го получаваш чрез dice
$this->_db = $db;
$this->_cfg = $cfg;
}
}

//...

$controller = $dice->create(Controller::class);
// готово - вече имаш инстанцииран контролер

По същия начин можеш да си извикваш и методите на класовете и dice ще ти ги обработи и ще подаде необходимите обекти. Страшно удобно ми се вижда.
Разбира се, и с трейтове става, но тук си много по-гъвкав - във всеки един момент можеш да си поискаш някакъв обект в аргументите и ще го получиш, без да се налага да пишеш отделни трейтове или да ровиш по целия проект за употребите на променения метод.
 
Добре де имам ето това ядро. Кажете според вас как да направя така, че ако в някой клас ми е нужна конфигурацията от ядрото, да я подам?

PHP:
<?php
namespace System\Core;

if (!file_exists($index = dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR."Index.php")) {
	die("The installed [$index] is not found!");
}

require_once $index;
require_once "Loader.php";

use \System\Core\Loader;
use \System\Core\Configuration;
use \System\Core\Controller;
use \System\Core\TemplateEngine;
use \System\Library\PDO;
use \System\Router\DefaultRouter;
use \System\Interfaces\DatabaseInterface;
use \System\Interfaces\RouterInterface;


class AppKernel
{
	private static $instance = null;
	private $configArray = null;
	private $db = null;
	private $router = null;
	
	private function __construct()
	{	
		/*Стартираме системният лоадър и регистрираме неймспейсове.*/
		Loader::registerNamespaces(
			array(
				"System" => FRAMEWORK_SYSTEM_FOLDER,
				"Model" => MODEL_FOLDER
			)
		);
		Loader::registerAutoLoad();
		/*Създаваме многослойна при нужда конфигурация*/
		$this->configArray[0] = (new Configuration)->setConfigFolder(FRAMEWORK_SETTINGS_FOLDER);
	}	
	
	/*Стартиране на контролера.*/
	public function runController()
	{
		Controller::getInstance();
		return $this;
	}
	
	/*Създаваме връзка с база данни!*/
	public function setDbConnection(DatabaseInterface $object = null,$dbname = null)
	{
		$cnf = $this->getConfiguration()->load('Library/DatabaseConnection');
		if (empty($dbname)) {
			$dbname = $cnf['name'];
		}
		if (empty($object)) {
			$this->db = new PDO($cnf['host'],$cnf['user'],$cnf['pass'],$dbname);
		} else {
			$this->db = $object;
		}
		
		return $this;
	}
	
	/*Взимане на връзката с база данни!*/
	public function getDbConnection()
	{
		if (empty($this->db)) {
			$this->setDbConnection();
		}
		return $this->db;
	}
	
	public function setRouter(RouterInterface $object = null) 
	{
		if (empty($object)) {
			$this->router = DefaultRouter::getInstance();
		} else {
			$this->router = $object;
		}
	}
	
	public function getRouter()
	{
		if (empty($this->router)) {
			$this->setRouter();
		}
		return $this->router;
	}
	
	public function getConfiguration($folder = 0)
	{
        if (isSet($this->configArray[$folder])) {
			return $this->configArray[$folder];
		}
		throw new \Exception("No such configuration folder key!");
    }
	
	public function getView()
	{
		return View::getInstance();
	}

	/*Вход към класът!*/
	public static function getInstance()
	{
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

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

Имам Trait:

PHP:
namespace System\Traits;

use \System\Core\AppKernel;
use \System\Core\Loader;

trait Configuration 
{
	public function getConfiguration($folder = 0,$configPath = null,$extension = '.php')
	{
		if (empty($configPath)) {
			foreach (Loader::getNamespaces() as $namespace => $namespacePath) {
				if (strpos(__CLASS__,$namespace) === 0) {
					$configPath = str_replace($namespace, "", __CLASS__);
					break;
				}
			}
		}
		return AppKernel::getInstance()->getConfiguration($folder)->load($configPath,$extension);
	}
}

и го добавям в класът Home така:

PHP:
class Home {
       use \System\Traits\Configuration;
        ...
}

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

ползвам същата логика с трейт. Общо взето си инклудвам методите с тяхните логики вътре в класовете, като те са централизирани и ако ми се наложи да променя нещо променям само в трейта не навсякъде по класовете. Чудил съм се с DI как ще стане но реално наскоро започнах да работя по този остарял проект и си правя така ъпдейти.
 
anonimen каза:
Разгледай този клас - https://r.je/dice.html
По време на самата конструкция на даден обект ти "връчва" това, което ти трябва. Например:

PHP:
class Controller {
private $_db, $_cfg;
__construct (Database $db, Config $cfg) { // тук си слагаш каквото ти трябва и автоматично го получаваш чрез dice
$this->_db = $db;
$this->_cfg = $cfg;
}
}

//...

$controller = $dice->create(Controller::class);
// готово - вече имаш инстанцииран контролер

По същия начин можеш да си извикваш и методите на класовете и dice ще ти ги обработи и ще подаде необходимите обекти. Страшно удобно ми се вижда.
Разбира се, и с трейтове става, но тук си много по-гъвкав - във всеки един момент можеш да си поискаш някакъв обект в аргументите и ще го получиш, без да се налага да пишеш отделни трейтове или да ровиш по целия проект за употребите на променения метод.

Имаш предвид още в ядрото да укажа какво ще му е нужно, но ако класът е сингълтон какво правим? Впрочем ядрото е сингълтон както ще се убедиш, и вместо и контролерът да е сингълтон мога в ядрото да проверявам дали е направена инстанция и реално погледнато да връщам старата инстанция и да я връщам още в ядрото. Да де ама контролера да е сингълтон май е по-добре?
 
Цялата ти концепция не е правилна според мен. Използваш static access.
Погледи Dependency Injection и Depencency Inversion.

Този блогпост на gatakka http://gatakka.eu/?p=1704 вкючва горе-долу добре обобщена информация по темата.


Trait-овере са парче код, без общо предназначение, което можеш да ползваш с цел кодът ти да бъде DRY.

Пример за trait:
PHP:
interface DatabaseAwareInterface
{
   public function setDatabase(DatabaseInterface $database = null);
}

trait DatabaseAwareTrait
{
    private $database;

    public function setDatabase(DatabaseInterface $database = null)
    {
         $this-database = $database;
    }

}

class Controller implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;
}

class Service implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;
}

class AlaBalaWithoutTrait implements DatabaseAwareInterface
{
    private $superdatabase;

    public function setDatabase(DatabaseInterface $database = null)
    {
         $this-superdatabase = $database;
    }
}


По този начин в DIC (Dependency Injection Contrainer) Можеш да добавиш един таг database.aware.

След това при инициализация ако има таг database.aware.
1. Приверяваш дали наследява DatabaseAwareInterface
2. След инициализация извикваш $obj->setDatabase(...)
 
gamena007 каза:
Цялата ти концепция не е правилна според мен. Използваш static access.
Погледи Dependency Injection и Depencency Inversion.

Този блогпост на gatakka http://gatakka.eu/?p=1704 вкючва горе-долу добре обобщена информация по темата.


Trait-овере са парче код, без общо предназначение, което можеш да ползваш с цел кодът ти да бъде DRY.

Пример за trait:
PHP:
interface DatabaseAwareInterface
{
   public function setDatabase(DatabaseInterface $database = null);
}

trait DatabaseAwareTrait
{
    private $database;

    public function setDatabase(DatabaseInterface $database = null)
    {
         $this-database = $database;
    }

}

class Controller implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;
}

class Service implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;
}

class AlaBalaWithoutTrait implements DatabaseAwareInterface
{
    private $superdatabase;

    public function setDatabase(DatabaseInterface $database = null)
    {
         $this-superdatabase = $database;
    }
}


По този начин в DIC (Dependency Injection Contrainer) Можеш да добавиш един таг database.aware.

След това при инициализация ако има таг database.aware.
1. Приверяваш дали наследява DatabaseAwareInterface
2. След инициализация извикваш $obj->setDatabase(...)

Трейтовете ги използвам точно по тази причина а именно кода ми да остане чист. По принцип използвам и интерфейси и прочие където трябва и се обработват данните, но някак си много започнах да ги ползвам и не е добре става претрупано от use ... Нещо логически ми бяга.
 
Аз мисля, че бъркаш някъде фундаментално. Седни попрочети за OOP и design patterns, започни от уроците на gatakka
 
lam3r4370 каза:
Аз мисля, че бъркаш някъде фундаментално. Седни попрочети за OOP и design patterns, започни от уроците на gatakka

Не просто когато писах системата преди година имах по-малко знания натрупани с опит. Иначе предложете ми някакъв хубав клас за да създавам инстанциите. Намерих един с рефлектор но все още се чуда дали да го ползвам:
http://krasimirtsonev.com/blog/article/Dependency-Injection-in-PHP-example-how-to-DI-create-your-own-dependency-injection-container
 
Можеш да използваш само този компонент ако искаш.

http://symfony.com/doc/current/components/dependency_injection/introduction.html
 
dakata__92 каза:
lam3r4370 каза:
Аз мисля, че бъркаш някъде фундаментално. Седни попрочети за OOP и design patterns, започни от уроците на gatakka

Не просто когато писах системата преди година имах по-малко знания натрупани с опит. Иначе предложете ми някакъв хубав клас за да създавам инстанциите. Намерих един с рефлектор но все още се чуда дали да го ползвам:
http://krasimirtsonev.com/blog/article/Dependency-Injection-in-PHP-example-how-to-DI-create-your-own-dependency-injection-container
Виж примера с dice, който ти дадох. Използва се изключително лесно, защото го настройваш само веднъж, и повече не се занимаваш да мислиш за него.
Така се получава, че почти никъде няма да имаш new Class или Class::getInstanse(), и това ще ти олекоти кода. Всичко, което ще правиш, е да зададеш в аргументите на методите кои точно класове ти трябват.

Разбира се, при теб може и да не се получи толкова удобно, защото досега си си ориентирал логиката към използването на traits, и ако трябва всичко да го пренапишеш ще стане каша (може би).
 
gamena007 каза:
Можеш да използваш само този компонент ако искаш.

http://symfony.com/doc/current/components/dependency_injection/introduction.html

Драснах си ето този клас:
PHP:
<?php
namespace System\Core;

class Container 
{
    private static $registry = array();

    public static function register($name, $resolve)
    {
        static::$registry[$name] = $resolve;
    }

    public static function resolve($name)
    {
        if (static::registered($name)) {
            $name = static::$registry[$name];
            return $name;
        }
        throw new Exception('Nothing registered with that name, fool.');
    }
 
    public static function registered($name)
    {
        return array_key_exists($name, static::$registry);
    }
}

Помзвайки го така:

PHP:
\System\Core\Container::register('Kernel',\System\Core\AppKernel::getInstance());
$kernel = \System\Core\Container::resolve('Kernel');
var_dump($kernel);

Но ми се струва, че някъде бъркам. Държа инстанции на класовете и прочие, но после ако някъде ми е нужен само и единствено да взема от кернела някакъв метод то ще трябва да направя в определеният клас да наследи Контейнера, и от там да има достъп до всичко в контейнера и то реално пак стигам до фундамента да наследявам ли контейнера, в главният ли контролер само да го ползвам, и изобщо пак трябва да му правя инстанции някъде си. Гледам, че на места се ползва ReflectionClass каква му е идеята?
 
Това цялото нещо за упражнение ли го правиш или ще го използваш? Ако е за упражнение - ок, но ако ще го ползваш по-добре не откривай топлата вода и използвай някой framework
 
lam3r4370 каза:
Това цялото нещо за упражнение ли го правиш или ще го използваш? Ако е за упражнение - ок, но ако ще го ползваш по-добре не откривай топлата вода и използвай някой framework
Ползвам го за упражнения и честно казано ако се получи добре то не ми пречи да си го ползвам за мои проекти. Топлата вода няма да я откривам, ама не ми харесва примерно в namespace да не съвпадат с реалното местоположение на файла. Просто един клас трябва с търсачката да го търся, или в други фреймове където за да построя едно просто сайтче от 4 странички ще трябва да заредя куп други омазии. Реално в момента искам да си създам собствена рамка, за мои проекти, която да се грижи за основните неща с прости класове, а не да прехвърлям топката от клас на клас държател на инстанциии и после да въвеждам инстанцията на контейнера и прочие. Да знам какво е DI, но знам че е и просто шаблон, който не е задължително да имплементирам според наложени от някого практики. Въвеждам си интерфейси подавам си ги където трябва и готово. Все пак отначало този фрейм бе базиран по логика на Иван Ванков, вследствие на което с много четене промених много нещата за да се получи както ще ми е удобно да работя с него. Няма да работи с Compooser поне за момента не го предвиждам, не ми се и налага. Имам си основно ядро в което съвсем спокойно мога да си оперирам, а реално погледнато то играе ролята на контейнер, защото управлява инстанциите на обектите от какъв тип да са и прочие. Нямам пакети и излишни дивотии, но без проблем ако ми трябва кеч на някой изглед в библиотеката ще си напиша клас за 30 минутки или ще го имам вече готов за ползване навсякъде в приложението. Осмисляйки всичко реално на мен AppKernel ми съдържа и управлява инстанциите. Въпроса ми беше друг. DI го ползвам, но в момента се чудя ако контролерът ми е сингълтон, как да му инжектирам конфигурацията за рутера примерно? Създавам си пътища водещи до ядрото, защото на мен не ми е нужно да наследявам всички методи на ядрото в контролера и ако някъде ми се наложи да наследя контролера класът да има достъп до parent методите. Нужен ми е само един метод, и правя инстанция на кернела в трейта след което разпространявам. Е вярно ако сменя името на класът ще трябва само в трейтовете да променя името на класът, което ме и узадачи, поради което попитах дали има някаква друга по-добра логика, но четейки установявам, че класът контейнер с неговите различни имплементации прилича на моят Кернел. Контейнера съхранява, но при мен ядрото е градивната единица. То отговаря за въвеждането на лоадъра, конфигурацията и прочие. То е първото нещо което се инициализира и на което се прави първата инстанция, от там нататък вече какво ще извикам, преди да извикам контролера и къде зависи изцяло от мен. Спазвам МVC затова не ми казвайте, че кодът не върши работа, защото си върши работата и то много добре. Модел, Изглед, Контролер - три думи, които много от фреймовете така са размили, че забравят колко просто може да стоят нещата. Кодът ми е подреден, придържащ се към правилата на PSR стандартите. Попитах Ви как да преправя трейтовете и дали са добра практика, а не да правите анализ на нещо което не сте видяли как работи и как е структурирано. Чета постоянно и непрекъснато, за да разбера дали греша. Затова, ще Ви помоля, мненията отностно това дали да го ползвам или не да се спестяват, ако не съм попитал. Въпросът в началото ми бе съвсем различен, но до момента само към различни готови прудукти ме пращате. Е, колеги, те не ме кефат. Предпочитам сам да стигна и открия топлата вода, за да знам защо се налага практически да е така, отколкото да ми налагат някакви ограничения с които да се съобразявам и да се чудя абе не може ли по по-лесен начин.
 
Добре де, какво ти пречи като вече имаш инстанция на Kernel-а, т.е $kernel = resolve(Kernel); да си викаш каквито искаш методи?

Singleton е anti-pattern.
И аз, и колегата ти дадохме достатъчно добри DI контейнери, а ти решаваш да си напишеш сам, просто няма логика това, което го правиш, нито е оптимално, нито е добро, затова ти казах да използваш framework-ци, все пак са разработвани от стотици и ако те не използват най-добрите практики, тогава кое?
Даже това, което ти правиш дето си наблъскал тези статични методи е ужас, знаеш ли как се тества после? Никак. Ако си гледал някой framework да го прави(laravel), то не си погледнал, че те са само фасада към динамичните методи.
Посочи едно ограничение, което си срещал например
 
lam3r4370 каза:
Добре де, какво ти пречи като вече имаш инстанция на Kernel-а, т.е $kernel = resolve(Kernel); да си викаш каквито искаш методи?

Singleton е anti-pattern.
И аз, и колегата ти дадохме достатъчно добри DI контейнери, а ти решаваш да си напишеш сам, просто няма логика това, което го правиш, нито е оптимално, нито е добро, затова ти казах да използваш framework-ци, все пак са разработвани от стотици и ако те не използват най-добрите практики, тогава кое?
Даже това, което ти правиш дето си наблъскал тези статични методи е ужас, знаеш ли как се тества после? Никак. Ако си гледал някой framework да го прави(laravel), то не си погледнал, че те са само фасада към динамичните методи.
Посочи едно ограничение, което си срещал например

Неискам да ползвам на готово. Идеята ми е да си блъскам главата за да може да го разбера защо точно се налага да имам Контейнер. Лесно ще дръпна Pimple или другите, но идеята ми е да имам главоблъсканица с идята за DI контейнер. Реално ако в него държа инстанция на ядрото то ще трябва първо да инициализирам контейнера и в него да слагам всичко останало. Ако някой изяви желание да ми драсне една структурна схема кое след кое да следва и дали го наследява като примерната която ще драсна аз.



1)index.php
- Инклудвам и правя инстанция на ядрото и стартирам контролера.
Framework { - папка
System { - папка
2) \System\Core\AppKernel::getInstance()
a) конструктор
- Въвежда авто лоадъра \System\Core\Loader и регистрира неймспейс областта и папката
- Въвежда конфигурацията \System\Core\Configuration
б) различни методи
- Осъществяване на конекция с базата и взимането и
- Връзка с рутера и взимането му
- Инстанция и на изгледа и взимането му
- стартиране на главният контролер
3) \System\Core\Controller::getInstance()
- Взимам от ядрото рутера и сетвам от конфигурацията дървото с пътища и
определям на коя страница се намира приложението за да извикам нейният вторичен контролер
- Контролерите отговарящи за страниците на рутера връщат резултат към главният контролер и
там се принтира реално сайтът.
4)\System\Library
- Системна библиотека съдържаща всякакви класове, които може да се наложе да се ползват. Сесиен хендлър,
база с данни драйвъри (MySQLi и PDO) ImageLab и всякакви дрги щуротийки.
}

Settings {
- папка съдържаща конфигурационните файлове
}
}
Model {
- папка в нея са темплейтите в отделна папка на сайта, контролерите на страниците,
вътре ще е и библиотеката на къстъм сайта, тоест User, Login, Navigation и всякакви класове свързани с
основната логика на сайта.
}



Просто казано това са основните неща и логика която спазвам. Споделете къде според вас е основната ми грешка?
 
Не мога да размера идеята защо кернела трябва да бъде наследяван от каквото и да е. Той трябва да движи приложението.

Пример:
1. Създаваш си обект от тип Request. Той съдържа информацията за заявката. Тук се вкючва абсолютно всичко - всички параметри, всички хедъри.

2. Създаваш си инстанция на Kernel класа.

3. Викаш $response = $kernel->handle($request);

4. $response->send();

5. Ако има неща, които трябва да се случат след изпращането на резултата - $kernel->terminate();

По този начин index.php трябва да има само:

PHP:
<?php

require_once __DIR__.'/autoload.php';

$kernel = new Kernel();

$request = Request::create();

$response = $kernel->handle($request);

$response->send();

$kernel->terminate();

В Kernel класа си инициализираш Container-a. $container = new Container();

За да има смисъл от контейнера, той не трябва да бъде пълен с инстанции, а с дефиниции. Т.е. ти трябва да му кажеш, че услугата database се създава от клас database с тези параметри и и се викат тези методи след инициализация (примерно задаване на достъп до контейнера ако е ContainerAware). Чак след като му кажа $db = $this->container->get('database'); трябва еднократно да се генерира услугата database и всяко нейно следващо поискване трябва да ми върне същият обект. По този начин ако в някоя страница не използвам базата данни, въобще няма да се създаде инстанция на обекта, връзка към нея и т.н. Това трябва да се прави с всяка услуга (имейл, бази данни, controller resolver-и и т.н.

След като си инициализирал контейнера, трябва да си намериш контролера посредством ControllerResolver. Този контролер трябва да наследява клас Controller, който да имплементира ContainerAwareInterface. След като се създаде контролера му се подава контаинера и от там тататък всичко е ясно.

Пример за $kernel->handle($request); Съжалявам за идентацията на кода. Пиша го в textarea.


PHP:
class Kernel
{

private $container;

public function boot()
{
    $this->container = new Container();
    $this->container->set('kernel', $this); // Ако имаме нужда от достъп до kernel-а някъде
}

public function handle(Request $request)
{
     $this->boot();
     .... Resolve controller and action
    if ($controller instanceof ContainerAwareInterface)
    {
         $controller->setContainer($this-container);
    }

    $response = call_user_func_array([$controller, $actionMethod], [$reqeust]);

    if (!$response instanceof Response) trow...

   return $response;
}

}

ПП. Това е не е работещ код. Написах го само за да схванеш идеята.

Виж как работи container-a тук: http://symfony.com/doc/current/components/dependency_injection/introduction.html
 

Back
Горе