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.
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'); //... } }
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?
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.
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.
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
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.
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?
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.
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."?
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.
https://www.gmail.com/intl/en/mail/help/about.
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.
Aihe on jo aika vanha, joten et voi enää vastata siihen.