Bug(バグ) #1594
完了デイリーニュースにガジェットが追加されている場合、デイリーニュース配信タスク実行時にエラーになる
100%
説明
Overview (現象)¶
デイリーニュースに1つ以上ガジェットが登録されている場合、
デイリーニュース配信タスクを実行するとエラーが表示され、デイリーニュースが1通も送信されない。
実行するタスク¶
$./symfony openpne:send-daily-news
表示されるエラー¶
Call to undefined method myUser::getMember.
再現バージョン¶
OpenPNE¶
- OpenPNE 3.7.0-dev
- OpenPNE 3.6beta12
php¶
- PHP 5.2.13
- PHP 5.3.3
- PHP 5.3.5
Causes (原因)¶
Way to fix (修正内容)¶
報告元¶
http://sns.openpne.jp/diary/25133 より転載
デイリーニュースが送れない、です。 PHP5.3.3の所為??それとも、3.7.0-devだから?? 【環境】 Powered by OpenPNE 3.7.0-dev # ./symfony plugin:list Installed plugins: symfony 1.4.6-stable openpne 3.7.0dev-beta opAuthMailAddressPlugin 1.3.1-devel opAuthMobileUIDPlugin 1.3.0-devel opAuthOpenIDPlugin 1.3.0-beta opCommunityTopicPlugin 1.0.0.2-stable opWebAPIPlugin 0.4.0-beta opDiaryPlugin 1.3.1-beta opBlogPlugin 1.0.1-stable opOpenSocialPlugin 1.2.0.1-stable opAshiatoPlugin 0.9.1-stable opMessagePlugin 0.9.1-beta opAlbumPlugin 0.9.4-beta opIntroFriendPlugin 0.9.0.1-beta opFavoritePlugin 1.0.0.3-beta opRankingPlugin 1.0.0-beta FreeBSD 7.2-RELEASE-p8 Apache/2.2.16 (FreeBSD) PHP 5.3.3 with Suhosin-Patch Zend Engine v2.3.0 mysql 5.1.36 【現象】 コマンドラインで #./symfony openpne:send-daily-news を実行すると Call to undefined method myUser::getMember. というエラーがでて、デイリーニュースが送れない。
原因¶
下記コマンドを実行した時にデイリーニュース用のガジェットが表示可能かのロジックが正しくない.
$ ./symfony openpne:send-daily-news
lib/task/openpneSendDailyNewsTask.class.php の下記部分が実行され,61 行目が実行される.
27 protected function execute($arguments = array(), $options = array()) 28 { ... 56 $filteredGadgets = array(); 57 if ($gadgets) 58 { 59 foreach ($gadgets as $gadget) 60 { 61 if ($gadget->isEnabled()) 62 { 63 $filteredGadgets[] = array( 64 'component' => array('module' => $gadget->getComponentModule(), 'action' => $gadget->getComponentAction()), 65 'gadget' => $gadget, 66 'member' => $member, 67 ); 68 } 69 } 70 }
ここで isEnabled() を見てみると,sfContext で得られる getUser() で使えるかどうかを決定している.しかし,タスクで実行しているためここで得られる User は実際にメールを送信したい Member を含む User ではなくタスクを実行した時の User (ここでは apps/api/lib/myUser.class.php )である.エラー自体はここで User から getMember() を呼び出すことができないという問題であるが,エラーが発生していなくても正しく動作しないものと思われる.
lib/model/doctrine/Gadget.class.php
66 public function isEnabled() 67 { 68 $list = $this->getGadgetConfigList(); 69 if (empty($list[$this->name])) 70 { 71 return false; 72 } 73 74 $controller = sfContext::getInstance()->getController(); 75 if (!$controller->componentExists($this->getComponentModule(), $this->getComponentAction())) 76 { 77 return false; 78 } 79 80 $member = sfContext::getInstance()->getUser()->getMember(); 81 $isEnabled = $this->isAllowed($member, 'view'); 82 83 return $isEnabled; 84 }
修正案¶
表示可能かどうかを見たいメンバを Gadget の isEnable() メソッドの引数に与えるようにする.
diff --git a/lib/model/doctrine/Gadget.class.php b/lib/model/doctrine/Gadget.class.php index 4cb5053..80ab1cd 100644 --- a/lib/model/doctrine/Gadget.class.php +++ b/lib/model/doctrine/Gadget.class.php @@ -63,7 +63,7 @@ class Gadget extends BaseGadget implements opAccessControlRecordInterface return $list[$this->name]['component'][1]; } - public function isEnabled() + public function isEnabled($member = null) { $list = $this->getGadgetConfigList(); if (empty($list[$this->name])) @@ -77,7 +77,10 @@ class Gadget extends BaseGadget implements opAccessControlRecordInterface return false; } - $member = sfContext::getInstance()->getUser()->getMember(); + if (is_null($member)) + { + $member = sfContext::getInstance()->getUser()->getMember(); + } $isEnabled = $this->isAllowed($member, 'view'); return $isEnabled; diff --git a/lib/task/openpneSendDailyNewsTask.class.php b/lib/task/openpneSendDailyNewsTask.class.php index f70f082..3a7d9a9 100644 --- a/lib/task/openpneSendDailyNewsTask.class.php +++ b/lib/task/openpneSendDailyNewsTask.class.php @@ -58,7 +58,7 @@ EOF; { foreach ($gadgets as $gadget) { - if ($gadget->isEnabled()) + if ($gadget->isEnabled($member)) { $filteredGadgets[] = array( 'component' => array('module' => $gadget->getComponentModule(), 'action' => $gadget->getComponentAction()),
問題2¶
- 携帯メールアドレスのみを持つメンバを追加する
- 携帯デイリーニュースにガジェットを追加する
- 「symfony openpne:send-daily-news」を実行する
- 下記エラーが発生する
PHP Fatal error: Cannot redeclare class defaultComponents in /home/hoge/sns/36.example.com/apps/mobile_frontend/modules/default/actions/components.class.php on line 50
- 下記エラーが発生する
原因¶
pc_frontend と mobile_frontend の defaultComponent が両方共ロードされることで redeclare としてエラーが発生する状態でした.
修正方針¶
同時に同じクラスがロードされることが原因だったため,実行する php のプロセスをそれぞれ別にすることでこの問題を回避する方針を取ります.
また,この修正のために php のバイナリを探しだす方法として下記 URL 先のものを参考にしました.
http://www.serverphorums.com/read.php?7,415337
https://github.com/sebastianbergmann/phpunit/issues/432
https://github.com/symfony/Process/blob/379b35a41a2749cf7361dda0f03e04410daaca4c/PhpExecutableFinder.php
修正案2¶
コンテキストの変更を 送信時ではなく $gadget->isEnabled() よりも前に行うことで原因で発生するような齟齬が発生しなくなる.
diff --git a/lib/task/openpneSendDailyNewsTask.class.php b/lib/task/openpneSendDailyNewsTask.class.php index e1c511f..303e9d6 100644 --- a/lib/task/openpneSendDailyNewsTask.class.php +++ b/lib/task/openpneSendDailyNewsTask.class.php @@ -22,20 +22,58 @@ Call it with: [php symfony openpne:send-birthday-mail|INFO] EOF; + + $this->addOptions( + array( + new sfCommandOption('app', null, sfCommandOption::PARAMETER_OPTIONAL, 'send to pc or mobile', null), + ) + ); } protected function execute($arguments = array(), $options = array()) { parent::execute($arguments, $options); - sfContext::createInstance($this->createConfiguration('pc_frontend', 'prod'), 'pc_frontend'); + $expectedOptions = array('pc_frontend', 'mobile_frontend'); + + if (isset($options['app'])) + { + if (in_array($options['app'], $expectedOptions)) + { + $this->sendDailyNews($options['app']); + } + else + { + throw new Exception('invalid option'); + } + } + else{ + $php = $this->findPhpBinary(); + foreach ($expectedOptions as $app) + { + exec($php.' '.sfConfig::get('sf_root_dir').'/symfony openpne:send-daily-news --app='.$app); + } + } + } + + private function sendDailyNews($app) + { + $isAppMobile = 'mobile_frontend' === $app; + $dailyNewsName = $isAppMobile ? 'mobileDailyNews' : 'dailyNews'; + $context = sfContext::createInstance($this->createConfiguration($app, 'prod'), $app); - $pcGadgets = Doctrine::getTable('Gadget')->retrieveGadgetsByTypesName('dailyNews'); - $mobileGadgets = Doctrine::getTable('Gadget')->retrieveGadgetsByTypesName('mobileDailyNews'); + $gadgets = Doctrine::getTable('Gadget')->retrieveGadgetsByTypesName($dailyNewsName); + $gadgets = $gadgets[$dailyNewsName.'Contents']; $targetMembers = Doctrine::getTable('Member')->findAll(); foreach ($targetMembers as $member) { + $address = $member->getEmailAddress(); + if ($isAppMobile !== opToolkit::isMobileEmailAddress($address)) + { + continue; + } + $dailyNewsConfig = $member->getConfig('daily_news'); if (null !== $dailyNewsConfig && 0 === (int)$dailyNewsConfig) { @@ -46,13 +84,6 @@ EOF; { continue; } - $address = $member->getEmailAddress(); - $gadgets = $pcGadgets['dailyNewsContents']; - $context = $this->getContextByEmailAddress($address); - if (opToolkit::isMobileEmailAddress($address)) - { - $gadgets = $mobileGadgets['mobileDailyNewsContents']; - } $filteredGadgets = array(); if ($gadgets) @@ -91,4 +122,36 @@ EOF; return in_array($day, opConfig::get('daily_news_day')); } + + private function findPhpBinary() + { + if (defined('PHP_BINARY') && PHP_BINARY) + { + return PHP_BINARY; + } + + if (false !== strpos(basename($php = $_SERVER['_']), 'php')) + { + return $php; + } + + // from https://github.com/symfony/Process/blob/379b35a41a2749cf7361dda0f03e04410daaca4c/PhpExecutableFinder.php + $suffixes = DIRECTORY_SEPARATOR == '\\' ? (getenv('PATHEXT') ? explode(PATH_SEPARATOR, getenv('PATHEXT')) : array('.exe', '.bat', '.cmd', '.com')) : array(''); + foreach ($suffixes as $suffix) + { + if (is_executable($php = PHP_BINDIR.DIRECTORY_SEPARATOR.'php'.$suffix)) + { + return $php; + } + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (is_executable($php)) { + return $php; + } + } + + return sfToolkit::getPhpCli(); + } + }