Я пытаюсь научиться правильно кодировать PHP ООП.
Здесь я сталкиваюсь с проблемами.
Я создал несколько классов, которые расширяют основной Application
класс, и я хочу, чтобы все работало правильно.
У меня есть основной файл index.php, который выглядит так:
include_once('classes/Application.php');
include_once('classes/Configuration.php');
include_once('classes/Database.php');
$app = new Application;
$config = new Configuration;
$db = new Database;
var_dump($app->db_connected);
var_dump($db->db_connected);
$db->connect($config->dbhost, $config->dbuser, $config->dbpass, $config->dbname);
var_dump($app->db_connected);
var_dump($db->db_connected);
Выход:
1. bool(false)
2. bool(false)
3. bool(false)
4. bool(true)
Мой основной файл приложения выглядит так:
class Application {
public $db_connected = false;
}
И мой класс базы данных выглядит так:
class Database extends Application {
function connect($dbhost, $dbuser, $dbpass, $dbname) {
if(!$this->db_connected) {
mysql_connect($dbhost, $dbuser, $dbpass) or die(mysql_error());
mysql_select_db($dbname) or die(mysql_error());
$this->db_connected = true;
}
}
}
Итак, вопрос в том, почему строка № 3 вывода index.php будет отображать false? Свойство db_connected было переопределено в классе Database и установлено в значение TRUE, но оно по-прежнему возвращает false.
Хотя при доступе напрямую из экземпляра класса Database он показывает TRUE правильно. В чем здесь дело?
Также, когда появляется команда class EXTEND? Всякий раз, когда создается экземпляр родительского класса, или мне приходится вручную создавать экземпляр дочернего класса?
Кажется, вы достигаете концепции static
Переменные все экземпляры класса совместно используют одну и ту же статическую переменную, поэтому использование нового дважды не будет проблемой.
Вы можете увидеть код на ideaone.
// your code goes here
class Application {
static $db_connected = false;
}
class Database extends Application {
function connect() {
if(!static::$db_connected) {
static::$db_connected = true;
}
}
}
$app = new Application;
$db = new Database;
var_dump(Application::$db_connected);
var_dump(Database::$db_connected);
$db->connect();
var_dump(Application::$db_connected);
var_dump(Database::$db_connected);
Ваш комментарий заставляет меня думать, что вы ищете лучший шаблон все вместе. Я хотел бы отбросить некоторые ключевые принципы, а именно OCP и LSP SOLID.
В этом случае вы бы избежали Application
быть примером Database
но вместо этого используйте внедрение зависимости. Вот переработанный код.
class Database {
private $db_connect = false;
public function connect () {
if(!$this->db_connect) { /* do connection */ }
}
}
class Application {
private $db;
public function setDatabse(Database $db) {
$this->db = $db;
}
public function getDatabase() {
return $this->db;
}
}
$db = new Database;
$app = new Application;
$app->setDatabase($db);
$app->getDatabase()->connect();
Эта строка — твой намек
Хотя при доступе напрямую из экземпляра класса Database он показывает TRUE правильно. В чем здесь дело?
У вас есть 2 экземпляра. Выше вы проверяете $db
экземпляр, с которым вы связаны, а затем вы печатаете из $app
который никогда не был связан. Это отдельные объекты, один связан, а другой нет.
Расширение происходит, как только файл загружается, читается интерпретатором php, это происходит независимо от того, используется ли когда-либо класс.
Extend вызывается от потомка и наследует все от класса, который расширяет. Поэтому, если вы вызываете дочерний метод у родителя, вы делаете это задом наперед. Это идет в одну сторону, Прент -> Дитя.
Я бы использовал Depenance инъекцию для базы данных, затем вы можете повторно использовать ее код.
Как это:
//parent class
class Application {
//holds a reference to the Database class
protected static $db_conn = false;
public function __construct($db){
self::$db_conn = $db;
}
}
//child class of Application
class Application2 extends Application {
public function getSomething($id){
return self::$db_conn->getbyId($id) ;
}
}
//separate utility class
class Database{
static $conn;
public function __construct( $dbhost, $dbname, $dbuser, $dbpass, $dbname) {
static::$conn = mysqli_connect($dbhost, $dbuser,$dbpass,$dbname);
}
public function getbyId( $id ){
..code to get stuff by id using $conn - previous connection ...
return $result;
}
}
$db = new Database("myhost", "myuser", "mypassw", "mybd");
$app = new Application2( $db );
$app->getSomething(1);
//create another app with the same database connection, this is the value of injecting it.
$second_app = new Application2( $db );
Смотрите, вы можете многократно использовать базу данных, вы можете заменить ее, не изменяя код в приложении, если вызовы функций класса Database не меняются. Каждая вещь ответственна за свою собственную business
,
Это называется разделением интересов.
Наследование хорошо, когда это необходимо. У вас может быть базовое приложение для бесплатных пользователей services
и затем расширить это с премиальным заявлением для платных участников. Чувство, что они заплатили, они получают всю бесплатную функциональность, но и премиальные вещи.
В моем примере выше база данных — это то, что им обоим нужно, так как другие вещи, вероятно, будут использовать это. Например, системе входа в систему может потребоваться подключение к базе данных, может потребоваться платежная система, корзина покупок. Это все отдельные объекты, они не должны и не должны выходить за пределы одного Master Class
Плохая идея. Держите их отдельно.
STATIC
Я видел упоминание о ::
оператор статического объекта. Мой пример немного ошибочен при использовании статического свойства protected static $db_conn = false;
$app = new Application2( $db );
$second_app = new Application2( $db ); //assigning db 2x is not needed.
Причина для ::
и ->
нормальный способ. Это статично ::
является общим для всего экземпляра класса, и ->
это просто экземпляр класса. Я назначил $db
Класс в статическую переменную в 2 раза лучше было бы так.
//parent class
class Application {
protected static $db_conn = false;
//separate method then construct.
public function connect($db){
self::$db_conn = $db;
}
}
//we'll keep the rest of the code the same here.
$db = new Database();
$app = new Application2();
$app->connect( $db );
$second_app = new Application2();
$second_app->getSomething(1);
Теперь в этом примере $second_app
никогда не запускал метод подключения. Но потому что первый $app
сделал и потому что static
для переменной базы данных protected static $db_conn
, Теперь все классы, которые расширили класс Application, имеют соединение с базой данных. Это то, что делает статический. Это значение является общим для всего экземпляра класса. Итак, когда вы видите ::
думать все экземпляры класса и когда вы видите ->
думаю только этот экземпляр класса. На самом деле это одна вещь, которая мне нравится в php, так как ее намного легче отслеживать, чем на некоторых других языках.
Не смущать вас, но другое использование ::
на самом деле не нужен экземпляр вообще. Предположим, у вас есть такой класс Config.
class Config{
static $db = 'hello';
static $items = array('one' => 'item 1' );
private __construct(){} // no construction allowed
static function getItem( $which ){
return self::$items[$which];
}
}
Теперь, даже не создавая экземпляр класса, вызывая new Config()
можно просто.
echo Config::$db;
// prints hello
echo Config::getItem('one');
// prints 'item 1'
Это довольно полно использовать для классов типа конфигурации. Где они представляют собой пустую оболочку, просто используемую для хранения данных, и вам не нужен объект для них, по сути, способ организовать вещи. Так что привязав это к предыдущим примерам
$db = new Database(Config::$myhost, Config::$myuser, Config::$mypassw, Config::$mybd);
В вашем случае лучшая практика ООП заключается в использовании медиатор шаблон. Конкретный посредник будет иметь класс применения:
class ApplicationBase {
private $db;
private $cfg;
public function setDb(Database $db) {
$this->db = $db; return $this;
}
public function setConfig(Config $cfg) {
$this->cfg = $cfg; return $this;
}
}
class Application extends ApplicationBase {
public function getDsn() {
return $this->cfg->getDsn();
}
public function getDbUser() {
return $this->cfg->getDbUser();
}
public function getDbPass() {
return $this->cfg->getDbPass();
}
public function getConnection() {
return $this->db->getConnection();
}
}
class AppComponent {
protected $app;
public function __construct(Application $app) {
$this->app = $app;
}
}
class Config extends AppComponent {
private $dsn;
private $dbuser;
private $dbpass;
// ... getters and setters
}
class Database extends AppComponent {
private $connection;
private function connect() {
$this->connection = new PDO(
$this->app->getDsn(),
$this->app->getUser(),
$this->app->getPass()
);
}
public function getConnection() {
if (null === $this->connection) $this->connect();
return $this->connection;
}
}
class Model extends AppComponent {
protected $table;
// Model stuff here
}
class Content extends Model {
public function getNews() {
$db = $this->app->getConnection();
return $db->query("SELECT * FROM $this->table LIMIT 5")->fetchAll();
}
}
Такой архитектуры будет достаточно для простых, чистых приложений, а классы будут готовы для простого модульного тестирования:
$app = new Application();
$cfg = new Config($app);
$db = new Database($app);
$app->setDb($db)->setConfig($cfg);
$content = new Content($app);
$news = $content->getNews();