Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: PHP: tietokantakysely ja oliot

AkeMake [27.08.2011 13:23:47]

#

Olen ihan uudella alueella tässä olio-ohjelmoinnissa. Yritän siis koodata yksinkertaisesti ohjelmaa, joka ottaa yhteyttä tietokantaan, suorittaa halutunlaisen kyselyn ja palauttaa saadut tulokset. Kopioin joitain Joomla!:n koodeja ja niitä muokkailin omaan tarkoitukseen sopivaksi. Ongelma on kuitenkin se, että Joomla! käyttää yhteyden muodostamiseen mysql_connect -functioa ja minä olen yrittänyt käyttää PDO:ta. Nyt en saa suoritettua execute -functioa vaan tästä tulee virheilmoitus Fatal error: Call to a member function execute() on a non-object in /Applications/MAMP/htdocs/.../database/database.php on line 142

Tuo rivi 142 on executedb -metodin kohta:

$query->execute($this->_values);

Olen kaikenlaisia oppaita lueskellut, mutta olio-ohjelmointi tuntuu edelleen suurelta tuntemattomalta. Olen kiitollinen, jos saan opastusta missä kaikkialla minulla koodi menee pieleen, miten viittauksia kannattaa/voi hyödyntää, milloin staattinen metodi on järkevä yms. yms...

index.php

/**
* Tässä välissä on tuotu tarvittavat tiedostot:
* config.php (sisältää TConfig-luokan, jonka sisällä tietokannan tiedot attribuuttien sisällä)
* object.php ja
* factory.php
*/

$db =& TFactory::getDBO();
$db->setQuery($queryoptions);

factory.php

class TFactory
{

	/**
	 * Get a configuration object
	 *
	 * Returns a reference to the global {@link JRegistry} object, only creating it
	 * if it doesn't already exist.
	 */
	function &getConfig($file = null, $type = 'PHP')
	{
		static $instance;

		if (!is_object($instance))
		{
			if ($file === null) {
				$file = TPATH_CONFIGURATION.DS.'config.php';
			}

			$instance = TFactory::_createConfig($file, $type);
		}

		return $instance;
	}

	/**
	 * Get a database object
	 *
	 * Returns a reference to the global {@link JDatabase} object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return object JDatabase
	 */
	function &getDBO()
	{
		static $instance;

		if (!is_object($instance))
		{
			//get the debug configuration setting
			$conf =& TFactory::getConfig();

			$instance = TFactory::_createDBO();
		}

		return $instance;
	}

	/**
	 * Create a configuration object
	 */
	function &_createConfig($file, $type = 'PHP')
	{
		// Hakee registry.php tiedoston
		timport('registry.registry');

		require_once $file;

		// Create the registry with a default namespace of config
		$registry = new TRegistry('config');

		// Create the TConfig object
		$config = new TConfig();

		// Load the configuration values into the registry
		$registry->loadObject($config);

		return $registry;
	}

	/**
	 * Create an database object
	 */
	function &_createDBO()
	{
		// Hakee database.php tiedoston
		timport('database.database');

		$conf =& TFactory::getConfig();

		$host 		= $conf->getValue('config.db_server');
		$user 		= $conf->getValue('config.db_user');
		$password 	= $conf->getValue('config.db_passwd');
		$database	= $conf->getValue('config.db_db');

		$options	= array ( 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database );

		$db =& TDatabase::getInstance( $options );

		return $db;
	}
}

object.php (Suoraan kopioitu Joomla!:lta, joten toimintaa en täysin ymmärrä)

class TObject
{

	/**
	 * A hack to support __construct() on PHP 4
	 *
	 * Hint: descendant classes have no PHP4 class_name() constructors,
	 * so this constructor gets called first and calls the top-layer __construct()
	 * which (if present) should call parent::__construct()
	 */
	function TObject()
	{
		$args = func_get_args();
		call_user_func_array(array(&$this, '__construct'), $args);
	}

	/**
	 * Class constructor, overridden in descendant classes.
	 */
	function __construct() {}

	/**
	 * Returns a property of the object or the default value if the property is not set.
 	 */
	function get($property, $default=null)
	{
		if(isset($this->$property)) {
			return $this->$property;
		}
		return $default;
	}


	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 */
	function set( $property, $value = null )
	{
		$previous = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;
		return $previous;
	}
}

registry.php (Tämäkin paljolti kopsattu, joten toimintaa en täydellisesti ymmärrä)

class TRegistry extends TObject
{
	/**
	 * Default NameSpace
	 * @var string
	 */
	var $_defaultNameSpace = null;

	/**
	 * Registry Object
	 *  - actually an array of namespace objects
	 * @var array
	 */
	var $_registry = array ();

	/**
	 * Constructor
	 */
	function __construct($namespace = 'default')
	{
		$this->_defaultNameSpace = $namespace;
		$this->makeNameSpace($namespace);
	}

	/**
	 * Create a namespace
	 */
	function makeNameSpace($namespace)
	{
		$this->_registry[$namespace] = array('data' => new stdClass());
		return true;
	}

	/**
	 * Get the list of namespaces
	 */
	function getNameSpaces()
	{
		return array_keys($this->_registry);
	}

	/**
	 * Get a registry value
	 */
	function getValue($regpath, $default=null)
	{
		$result = $default;

		// Explode the registry path into an array
		if ($nodes = explode('.', $regpath))
		{
			// Get the namespace
			//$namespace = array_shift($nodes);
			$count = count($nodes);
			if ($count < 2) {
				$namespace	= $this->_defaultNameSpace;
				$nodes[1]	= $nodes[0];
			} else {
				$namespace = $nodes[0];
			}

			if (isset($this->_registry[$namespace])) {
				$ns = & $this->_registry[$namespace]['data'];
				$pathNodes = $count - 1;

				//for ($i = 0; $i < $pathNodes; $i ++) {
				for ($i = 1; $i < $pathNodes; $i ++) {
					if((isset($ns->$nodes[$i]))) $ns =& $ns->$nodes[$i];
				}

				if(isset($ns->$nodes[$i])) {
					$result = $ns->$nodes[$i];
				}
			}
		}
		return $result;
	}

	/**
	 * Load the public variables of the object into the default namespace.
	 */
	function loadObject(&$object, $namespace = null)
	{
		// If namespace is not set, get the default namespace
		if ($namespace == null) {
			$namespace = $this->_defaultNameSpace;
		}

		if (!isset($this->_registry[$namespace])) {
			// If namespace does not exist, make it and load the data
			$this->makeNameSpace($namespace);
		}

		/*
		 * We want to leave groups that are already in the namespace and add the
		 * groups loaded into the namespace.  This overwrites any existing group
		 * with the same name
		 */
		if (is_object( $object ))
		{
			foreach (get_object_vars($object) as $k => $v) {
				$this->_registry[$namespace]['data']->$k = $v;
			}
		}

		return true;
	}
}

database.php

<?php

class TDatabase extends TObject {

	var $_values		= array();

	var $_query			= '';

	/**
	 * Returns a reference to the global Database object, only creating it
	 * if it doesn't already exist.
	 *
	 * The 'driver' entry in the parameters array specifies the database driver
	 * to be used (defaults to 'mysql' if omitted). All other parameters are
	 * database driver dependent.
	*/
	function &getInstance( $options	= array() )
	{
		static $dbo;

		if (!is_object($dbo))
		{

			$path	= dirname(__FILE__).DS.'mysql.php';

			if (file_exists($path)) {
				require_once($path);
			}

			$dbo	= new TDatabaseMySQL($options);
		}

		return $dbo;
	}

	function executedb()
	{
		$query = $this->prepare($this->_query);
		$query->execute($this->_values);
		$value = $query->fetchAll(PDO::FETCH_ASSOC);
		return $value;
	}

	function setQuery($options) {

		/**
		* Käsitellään $options -taulukko ja laitetaan valmis kysely _query -attribuuttiin
		* ja execute() -function sisään tuleva taulukko _values -attribuutin sisään.
		*/

		return $this->executedb();
	}
}

mysql.php

class TDatabaseMySQL extends TDatabase
{

	/**
	* Database object constructor
	*/
	function __construct( $options )
	{
		$host		= array_key_exists('host', $options)	? $options['host']		: 'localhost';
		$user		= array_key_exists('user', $options)	? $options['user']		: '';
		$password	= array_key_exists('password',$options)	? $options['password']	: '';
		$database	= array_key_exists('database',$options)	? $options['database']	: '';

		try {
			$lnk = new PDO("mysql:host=".$host.";dbname=".$database, $user, $password);
			$lnk->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			$lnk->exec('SET CHARACTER SET utf8');
		} catch (PDOException $e) {
			TErrors::setError('Could not connect to MySQL');
		}
		return $lnk;
	}

	/**
	 * Database object destructor
	 */
	function __destruct()
	{
		$return = false;
		unset($lnk);
		return $return;
	}

	function prepare()
	{
		return;
	}

	function execute()
	{
		return;
	}
}

-tossu- [27.08.2011 14:28:16]

#

Kuten virheilmoitus sanoo, $query ei ole olio, joten et voi kutsua sen execute-funktiota. Syy löytyy edelliseltä riviltä: $this->prepare ei palauta oliota. $this on tässä tapauksessa TDatabaseMySQL-tyyppinen olio, ja jos katsot sen prepare-funktiota, huomaat varmaan, ettei se palauta mitään.

En jaksa lukea koko koodia läpi, mutta TDatabaseMySQL-luokassa on muutakin vikaa, joten suosittelen, että luet Putkan uuden PHP-oppaan olio-ohjelmointia käsittelevän osan: https://www.ohjelmointiputka.net/oppaat/opas.php?tunnus=php_14

AkeMake [27.08.2011 23:56:07]

#

Juu, nuo TDatabaseMySQL -luokan prepare ja execute metodit ovat melko turhia. Ongelmanihan on varmaan siinä, että käytän TDatabaseMySQL -tyyppistä oliota, josta kyllä saan ulos kyselyt oikeassa muodossaan, mutta en voi lähettää kyselyitä, koska tuo olio ei sisällä PDO -luokkaa. TDatabaseMySQL -luokan konstruktori sisältäisi kyllä olion $lnk, joka käyttää PDO -luokkaa, mutta ongelma on etten osaa ottaa tuota $lnk -olioa käyttöön.

...

Samalla kun tätä kirjoitin mietin mitä kaikkea virhettä tuossa TDatabaseMySQL -luokassa on ja aloin korjata niitä. Siinä samalla tuli huomattua miten saan tuotua tuon $lnk -olion (muutin nyt kuvaavammaksi $db-olioksi) käyttöön. Pienellä kikkailulla sain tämän toimimaan. :D Jännä miten ongelman selittäminen muille (tämän viestin ensimmäisen kappaleen kirjoittaminen) voikaan avata ongelmaa itselle niin, että se ratkeaa samalla kun sitä kysyy. :P

mysql.php näyttäisi nyt tältä

class TDatabaseMySQL extends TDatabase
{
	var $_db		= '';
	var $_host		= '';
	var $_user		= '';
	var $_password	= '';
	var $_database	= 'localhost';

	/**
	* Database object constructor
	*/
	function __construct( $options )
	{
		$this->_host		= array_key_exists('host', $options)	? $options['host']		: 'localhost';
		$this->_user		= array_key_exists('user', $options)	? $options['user']		: '';
		$this->_password	= array_key_exists('password',$options)	? $options['password']	: '';
		$this->_database	= array_key_exists('database',$options)	? $options['database']	: '';
		$this->_db			= $this->getdb();
		$this->getdb();

	}

	/**
	 * Database object destructor
	 */
	function __destruct()
	{
		$return = false;
		unset($this->_db);
		return $return;
	}

	/**
	* Open the database connection and returns the database object
	*/
	function getdb() {
		try {
			$db = new PDO("mysql:host=".$this->_host.";dbname=".$this->_database, $this->_user, $this->_password);
			$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			$db->exec('SET CHARACTER SET utf8');
		} catch (PDOException $e) {
			TErrors::setError('Could not connect to MySQL');
		}
		return $db;
	}

	/**
	 * Sets given value to given property
	 */
	function setValue($key, $value)
	{
		$this->$key = property_exists('TDatabase', $key) ? $value : $this->$key;
	}

	/**
	 * Returns value of given property
	 */
	function getValue($key)
	{
		return $this->$key;
	}
}

Tuo putkan php-opas tuli minulle täysin yllätyksenä. Olen kyllä lukenut nämä muut oppaat (MySQL ja PHP, arkiston Käytännön PHP-opas), mutta tätä linkkaamaasi en ollut koskaan huomannut. Onkohan tuo ollut kauankin olemassa? Ei sitä ainakaan muiden oppaiden joukosta löydy.

Putkan olio-ohjelmointia käsittelevä opas oli kyllä ehdottomasti kaikkein selkein mitä olen netistä löytänyt. Vihdoinkin ymmärsin myös kunnolla, että miten ihmeessä se MVC-malli toimii. Aikaisemmin olin ihmetellyt, että millaista koodia mihinkin osaan mahtaa tulla. Mahtava opas!

Metabolix [28.08.2011 00:07:40]

#

MVC-malli ei edellytä luokkien käyttöä ensinkään. Se edellyttää vain koodin jaottelun niin, että datan käsittely on yhdessä paikassa (M), sivujen tulostus toisessa paikassa (V) ja käyttäjän syötteen käsittely kolmannessa paikassa (C). Tällainen jaottelu on järkevä ihan nimestä tai teorian tuntemisesta riippumatta: logiikka pysyy helpommin hallinnassa ja erillään tulostuksesta, ja myöskään mahdolliset virhetilanteet eivät tapahdu kesken tulostuksen vaan jo syötteen käsittelyn yhteydessä, jolloin on helppo jättää sivu tulostamatta ja näyttää sen sijaan virheilmoitus.

Myöskään noilla pitkillä koodeillasi ei ole mitään tekemistä sen kanssa, noudattaako kokonaisuus MVC-mallia vai ei. Nuo luokat ovat vain ylimääräinen kerros itse sovelluksen ja tietokannan välissä, ja tarkoituksena on ilmeisesti tehdä yhtenäinen rajapinta, jota sovellus voi käyttää taustalla olevasta tietokannasta riippumatta.

Mainittu uusi PHP-opas on tosiaan aivan uusi, vasta julkaisua odottamassa, lue keskustelu.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta