Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: Zend Framework 2, error: function get()

Sivun loppuun

Request [18.06.2014 09:56:53]

#

Terve,

Olen aloittelija Zendin kanssa ja törmäsin seuraavaan ongelmaan...

Kun teen esimerkiksi seuraavanlaisen luokan:

namespace Application\Model\Password;
use Zend\Mvc\Controller\AbstractActionController;

class PasswordEncrypt extends AbstractActionController {
    public function getStaticSalt() {
		$config = $this->getServiceLocator()->get('Config');
		return $config['static_salt'];
	}
}

Ja kutsun luokkaa RegistrationController -luokassa, joka on Controllerin alapuolella:

$password = new PasswordEncrypt();
$password->getStaticSalt();

Niin Zend Framework 2 antaa seuraavan ilmoituksen: Fatal error: Call to a member function get() on a non-object

Sama juttu jos teen getillä näin

$this->em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');

ja kutsun sitä muualla, niin aina tulee getistä virhettä.

Yritin myös lisätä module.conf.php -tiedostoon seuraavan rivin:

return array(
    'service_manager' => array(
        'invokables' => array(
            'Application\Model\Password' => 'Application\Model\Password',
        ),
    ),
);

Mutta tämä ei auttanut. Eli mitä pitäisi tehdä, että saan getin toimimaan? En myöskään haluaisi viedä

$this->getServiceLocator()->get('Config');

esimerkiksi parametrina PasswordEncrypt -luokalle sisään. Yritin myös toteuttaa PasswordEncrypt -luokan seuraavan rajapinnan avulla: implements ServiceLocatorAwareInterface. Antaa saman virheen silti.

qeijo [18.06.2014 11:34:08]

#

Tuossa kohtaa suoritusta ServiceLocatorAwareInterface:sin rajapinnan pakottamaa riippuvuutta (ServiceLocator) ei vielä oli injektoitu ohjaimeen. Muutenkin tapasi käyttää zendiä ja MVC:tä on väärä, vähintäänkin sekava.

Esimerkiksi:

<?php

namespace Application\Service;

use Doctrine\ORM\EntityManager;
use Application\Entity\User;

class RegistrationService {

	/**
	 * @var EntityManager
	 */
	protected $em;

	/**
	 * @var array
	 */
	protected $config;

	/**
	 * @param EntityManager $em
	 * @param array $config
	 */
	public function __construct(EntityManager $em, $config) {

		$this->em 	  = $em;
		$this->config = $config;
	}

	/**
	 * @param User $user
	 */
	public function register(User $user) {

		//Tähän lisäät kaikki EI käyttötapaukseen/ohjaimeen kuuluva, kuten:

		$staticSalt = $this->getStaticSalt();
	}

	protected function getConfig() {

        return $this->config;
    }

	protected function getStaticSalt() {

		$config = $this->getConfig();

        if(!isset($config['static_salt']) || empty($config['static_salt']))
             throw new Excpetion('RegistrationService misconfiguration!');

        return $config['static_salt'];
    }
}
<?php

namespace Application\Factory;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Service\RegistrationService;

class RegistrationServiceFactory implements FactoryInterface {

    /**
     * @param ServiceLocatorInterface $serviceLocator
	 */
    public function createService(ServiceLocatorInterface $serviceLocator) {

		$em 	= $serviceLocator->get('Doctrine\ORM\EntityManager');
        $config = $serviceLocator->get('Config');
        $config = isset($config['registration_config']) ? $config['registration_config'] : array());

        //Injektoidaan riippuvuudet
    	return new RegistrationService($em, $config);
    }
}
<?php

class Module {

	public function getServiceConfig() {

        return array(
            'factories' => array(
				'RegitrationServiceFactory' => 'Application\Factory\RegitrationServiceFactory',
            ));
	}

	//...
}

Varsinainen käyttötapaus:

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class RegistrationController extends AbstractActionController {

    public function registerAction() {

    	$service = $this->getServiceLocator()->get('RegistrationServiceFactory');
        //...
    }
}

Request [18.06.2014 14:30:05]

#

Esimerkkisi avulla kyseinen homma lähti toimimaan, joten kiitos! Olen Zendiä käyttänyt muutamia päiviä vasta ja sen periaate ei ole vielä läheskään tuttua. Ilmeisesti tätä qeijon näyttämää esimerkkiä voi kuitenkin soveltaa nyt aika pitkälle.

ps. tämä kohta oli varmasti tarkoitettu näin:

protected function getConfig() {
     //vanha: return $config;
     return $this->config;
}

Lisäys:

Request kirjoitti:

Esimerkkisi avulla kyseinen homma lähti toimimaan, joten kiitos! Olen Zendiä käyttänyt muutamia päiviä vasta ja sen periaate ei ole vielä läheskään tuttua. Ilmeisesti tätä qeijon näyttämää esimerkkiä voi kuitenkin soveltaa nyt aika pitkälle.

ps. tämä kohta oli varmasti tarkoitettu näin:

protected function getConfig() {
     //vanha: return $config;
     return $this->config;
}

En huomannutkaan, että olit lisännyt vielä seuraavan metodin RegistrationService -luokkaan:

public function register(User $user) {
        //Tähän lisäät kaikki EI käyttötapaukseen/ohjaimeen kuuluva, kuten:
		$staticSalt = $this->getStaticSalt();
}

Voitko vielä vähän avata tämän käyttöä enemmän?

Metabolix [18.06.2014 16:15:28]

#

Request kirjoitti:

Kun teen esimerkiksi seuraavanlaisen luokan: – – PasswordEncrypt

Hieman aiheen ohi, mutta onko jokin järkevä syy tehdä itse luokkaa salasanan käsittelyyn, ja et kai oikeasti enkryptaa salasanoja, kun pitäisi laskea tiiviste? PHP:ssä on valmiiksi turvallinen tiivistefunktio password_hash, ja on varmasti tehty myös tähän nojaavia Zend-luokkia.

The Alchemist [18.06.2014 20:56:33]

#

Ei tuohon mitään tarvitse netistä etsiä, kun Zendissä on vakiona Bcrypt-tiivistäjä (Zend\Crypt\Password\Bcrypt). Käyttäisin itse esimerkiksi ZfcUser-moduulia rekisteröitymisen ja kirjautumisen lisäämiseen järjestelmään valmiina palikkana.

Request [19.06.2014 10:20:32]

#

Jep, otin tuon Zendin Bcrypt-tiivistäjän käyttöön (Zend\Crypt\Password\Bcrypt). Hyviä tutoriaaleja voi myös vinkata jos niitä tietää Zend 2:sta. Nyt olen lähinnä katsonut näitä esimerkkejä: https://github.com/wingman007/fmi ja tehnyt tämän: http://framework.zend.com/manual/2.0/en/user-guide/overview.html

qeijo [19.06.2014 12:55:11]

#

Request kirjoitti:

En huomannutkaan, että olit lisännyt vielä seuraavan metodin RegistrationService -luokkaan:

public function register(User $user) {
        //Tähän lisäät kaikki EI käyttötapaukseen/ohjaimeen kuuluva, kuten:
        $staticSalt = $this->getStaticSalt();
}

Voitko vielä vähän avata tämän käyttöä enemmän?

On syytä jakaa ja piilottaa toteutukset pienempiin loogisiin osiin eikä paisuttaa ohjaimia turhalla tiedolla ja toistuvalla koodilla.

Ei siis näin:

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\Crypt\Password\Bcrypt;

class RegistrationController extends AbstractActionController {

	public function registerAction() {

    	$form 		= new RegisterForm();
		$em 		= $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
		$hydrator	= new DoctrineObject($em, 'Application\Entity\User');
		$request 	= $this->getRequest();

		$form->setHydrator($hydrator);
    	$form->bind(new User());

		if($request->isPost() AND $form->setData($request->getPost())->isValid()) {

			$user   = $form->getData();
			$bcrypt = new Bcrypt();

			$password = $bcrypt->create($user->getPassword());

			$user->setPassword($password);

			$em->persist($user);
			$em->flush();

			$this->flashMessenger()->addMessage('Käyttäjä lisätty onnistuneesti..');
			return $this->redirect()->toRoute('registration');
		}

    	return new ViewModel(array(
    		'form' 	   => $form,
    		'messages' => $this->flashMessenger()->getMessages(),
    	));
	}
}

Vaan esimerkiksi näin:

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class RegistrationController extends AbstractActionController {

    public function registerAction() {

    	$service = $this->getServiceLocator()->get('RegisterServiceFactory');
    	$form    = $this->getServiceLocator()->get('RegisterFormFactory'); //Bindaukset, hydraattorit ja muut valmistelut hoidetaan "tehtaassa"
    	$request = $this->getRequest();

    	if($request->isPost() AND $form->setData($request->getPost())->isValid()) {

    		$user = $service->registerUser($form->getData());

    		$this->flashMessenger()->addMessage('Käyttäjä lisätty onnistuneesti..');
    		return $this->redirect()->toRoute('registration');
    	}

    	return new ViewModel(array(
    		'form' 	   => $form,
    		'messages' => $this->flashMessenger()->getMessages(),
    	));
	}
}
<?php

namespace Application\Service;

use Application\Entity\User;

class RegistrationService {

    /**
     * @var UserService
     */
	protected $userService;

    /**
     * @var CryptService
     */
	protected $cryptService;

    /**
     * @param UserService $userService
     * @param CryptService $cryptService
     */
	public function __construct(UserService $userService, CryptService $cryptService) { //Vaihtoehtoisesti suoraan PasswordInterface CryptService:in tilalla

		$this->userService  = $userService;
        $this->cryptService = $cryptService;
	}

    //..

    /**
     * @param User $user
	 * @return User
     */
	public function registerUser(User $user) {

		$password = $this->getCryptService()->create($user->getPassword());

		$user->setPassword($password)
			->setCreated(new DateTime('now'));

		$user = $this->getUserService()->save($user);

		return $user;
	}

	//..
}
<?php

namespace Application\Service;

use Doctrine\ORM\EntityManager;
use Application\Entity\User;

class UserService {

    /**
     * @var EntityManager
     */
    protected $em;

    /**
     * @param EntityManager $em
     */
    public function __construct(EntityManager $em) {

        $this->em = $em;
    }

    //..

    /**
     * @param User $user
	 * @return User
     */
	public function save(User $user) {

		$this->getEntityManager()->persist($user);
		$this->getEntityManager()->flush($user);

		return $user;
	}
}

CryptService luokan "tehdas" voisi sitten injektoida palveluun minkä tahansa PasswordInterface:in toteuttavan luokan, esim juuri Zend\Crypt\Password\BCrypt.

<?php

namespace Application\Service;

use Zend\Crypt\Password\PasswordInterface;

class CryptService {

    /**
     * @var PasswordInterface
     */
	protected $crypto;

    /**
     * @param PasswordInterface $crypto
     */
	public function __construct(PasswordInterface $crypto) {

		$this->crypto = $crypto;
	}

    //..

	public function create($plaintext) {

		return $this->getCrypto()->create($plaintext);
	}

	public function verify($plaintext, $hash) {

		return $this->getCrypto()->verify($plaintext, $hash);
	}
}

Esimerkissä yksinkertaistetaan mm. virheidenhallinta. Lisäksi CryptService:n sijasta voisi harkita injektoida PasswordInterface suoraan RegisterService:een, riippuen siitä mitä kaikkea ajattelit CryptService luokkaan toteuttaa, esim mainitsemasi staticSalt hommat etc.

Request [27.06.2014 11:34:51]

#

Onko qeijo:lla tai muilla vielä vinkkiä kielikäännöksen tekemiseen nappia painamalla?

Pitääkö routeen lisätä esimerkiksi näin:

'router' => array(
        'routes' => array(
            'home' => array(
                'type' => 'Literal',
                'options' => array(
                    'route'    => '/',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'language' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '[:lang/]',
                            'defaults' => array(
                                'lang' => 'en',
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),

ja sitten painike ohjaa aina tiettyyn urliin esimerkiksi /zend.localhost/en/ vai mikä on zendissä helppo/hyvä tapa tehdä kielikäännökset nappia painamalla?

Selaimen kielen perusteella oleva käännös on näyttävästi helppo tehdä näin (täytyy tosin lisätä jokaiseen moduuliin erikseen?):

namespace Application;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);

        $translator = $e->getApplication()->getServiceManager()->get('translator');

        $translator
        ->setLocale(\Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']))
        ->setFallbackLocale('en_US');
    }

//..

Tässä on yksi tutoriaali
Localizing application in Zend Framework 2
, mutta eikö Zendissä ole mitään "helpompaa" tapaa tehdä tätä? Esimerkiksi, että painikkeelta tulisi tieto: public function onBootstrap(MvcEvent $e)-metodille ja sitten voitaisiin setata painikkeen valuen perusteella vaikka uusi locale? Ja jos tämä on mahdollista, niin miten se mahtaa tapahtua?

qeijo [28.06.2014 22:38:51]

#

Kaikki onBootstrap - metodit suoritetaan joten käännöskoodia ei tarvitse toistaa. Selaimen mukaan valitaan kieli esim. just esittämälläsi tavalla. Sen lisäksi voit luoda reitin joka sisältää kieliosan. Lisäät vain eventin bootrapissa esim. EVENT_ROUTE tapahtumaan joka tarkistaa mahdollisen kielen ja asettaa sen manuaalisesti.

Request [29.06.2014 21:21:55]

#

Eli tarkoitatko, että kannattaa tehdä esimerkiksi LanguageController, joka suorittaa X-metodin kun nappia painetaan? Metodissa voitaisiin sitten tehdä näin:

$sessionContainer = new Container('locale');

if(!$sessionContainer->offsetExists('mylocale')){
    if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){
        $sessionContainer->offsetSet('mylocale', Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']));
    }else{
        $sessionContainer->offsetSet('mylocale', 'en_US');
    }

}

return $sessionContainer->mylocale;

Joka sitten asetettaisiin onBootstrap:ssa jotenkin näin:

$translator = $e->getApplication()->getServiceManager()->get('translator');
$translator ->setLocale($e->mylocale)->setFallbackLocale('en_US');

Tämä tarkoittaa, että kielen ei tarvitse pyöriä urlissa? Vai mitä tarkoitit "Lisäät vain eventin bootrapissa esim. EVENT_ROUTE tapahtumaan joka tarkistaa mahdollisen kielen ja asettaa sen manuaalisesti."?

qeijo [30.06.2014 13:01:59]

#

Request kirjoitti:

Vai mitä tarkoitit "Lisäät vain eventin bootrapissa esim. EVENT_ROUTE tapahtumaan joka tarkistaa mahdollisen kielen ja asettaa sen manuaalisesti."?

<?php

public function onBootstrap(MvcEvent $e) {

    $eventManager = $e->getApplication()->getEventManager();

	$eventManager->attach(MvcEvent::EVENT_ROUTE, function($e) {

		$lang = $e->getRouteMatch()->getParam('lang');

		//Aseta kieli parametrin mukaan...
        $translator = $e->getApplication()->getServiceManager()->get('translator');

	});
}

Request kirjoitti:

Eli tarkoitatko, että kannattaa tehdä esimerkiksi LanguageController, joka suorittaa X-metodin kun nappia painetaan?
..
Tämä tarkoittaa, että kielen ei tarvitse pyöriä urlissa?

Mielestäni jos sivustolla on useampi kieli tarjottavana on syytä pystyä avaamaan esim. ulkoisen linkin suoraan oikealla kielellä:

https://www.gmail.com/intl/fi/mail/help/about.html
https://www.gmail.com/intl/en/mail/help/about.html

Request [30.06.2014 19:27:19]

#

Kiitos qeijo! Ihmettelinkin, että miksi MvcEvent $e ei löydä suoraan lang -parametria, mutta se pitikin toteuttaa näyttämälläsi tavalla.

Koko kielivaihdon tein suunnilleen näin:

1. lisäsin uuden lang -routerin module.config.php -tiedostoon.
2. Pudostusvalikossa olevilla kielivalinnoilla on jokaisella oma linkki, joka ohjaa LanguageControlleriin kuten näin:

<li><a href="<?php $this->url('lang/setting', array('lang' => 'fi_FI')); ?>"><?php $this->translate('Suomi'); ?></a></li>
<li><a href="<?php $this->url('lang/setting', array('lang' => 'en_US')); ?>"><?php $this->translate('Englanti'); ?></a></li>

3. LanguageControllerin indexAction():ssa asetetaan urlin kautta tuleva kieli esimerkiksi fi_FI sessioon seuraavasti:

$sessionContainer->mylocale = $this->params('lang');

4. Tämän jälkeen otetaan viimeinen osoite, jossa kielivaihdos tehtiin seuraavasti:

$_SERVER['HTTP_REFERER']

Sitten käyttäjä ohjataan indexAction():sta samalle sivulle missä hän olikin seuraavasti:

$this->redirect()->toUrl($_SERVER['HTTP_REFERER']);

5. Sitten onBootstrap -metodi katsoo joka kerta $sessionContainer->mylocale; -sessiosta, että mikä kieli sinne on viimeksi asetettu, joka sitten asetetaan näin onBootstrap:ssa:

$translator->setLocale($language->getLanguageSession())->setFallbackLocale('en_US');

Tuossa nyt pieni selitys ihan vain siksi, jos jollekkin on joskus hyötyä. Ei varmasti Zendissä parhain tapa toteuttaa, mutta ainakin toimii.

PS. Tällä systeemillä kieli ei tosin näy selaimen osoiterivillä, mutta ei tarvinnut lisätä kaikkiin routereihin [:lang] -parametria.


Sivun alkuun

Vastaus

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

Tietoa sivustosta