I have some php code in a site that I'm trying to move to a different server (from development into production) similar to the following:
class test1 {
static function make () {
$object = static::maker(function( $params ) {
return new static($params);
});
return $object;
}
protected static function maker ( $callable ) {
$params = [/*...*/];
return $callable( $params );
}
}
class test2 extends test1 {
function sayhi () {
echo "Hello! from: ".get_called_class();
}
}
$test = test2::make();
$test->sayhi();
My development environment is OSX 10.9 running php 5.5.17 and nginx with php-fpm, and my production environment is Ubuntu 14.04.1 LTS running php 5.5.11 and nginx with php-fpm.
on my development machine I get the expected result:
Hello! from: test2
but on the production server it throws an error:
PHP message: PHP Fatal error: Call to undefined method test1::sayhi()
revealing that the static context is pointing to test1 which isn't expected.
The workaround I came up with is to call the function like this:
$class = get_called_class();
$object = static::maker(function( $params ) use ( $class ) {
return new $class($params);
});
My question is: Why is it different between versions? And can someone shed some light on what's the expected behavior?
Thanks!
I found the answer to my question: late static bindings.
https://bugs.php.net/bug.php?id=67774
http://php.net//manual/en/language.oop5.late-static-bindings.php
Related
Assume the following simple web app:
<?php
// src/App/App.php
namespace Practice\Sources\App;
use Closure;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
class App implements RequestHandlerInterface
{
private Closure $requestProvider;
private ResponseFactoryInterface $responseFactory;
private SapiEmitter $responseEmitter;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->requestProvider = fn() => ServerRequestFactory::fromGlobals();
$this->responseFactory = $responseFactory;
$this->responseEmitter = new SapiEmitter();
}
public function run(): void
{
$request = ($this->requestProvider)();
$response = $this->handle($request);
$this->responseEmitter->emit($response);
}
public function handle(RequestInterface $request): ResponseInterface
{
$response = $this->responseFactory->createResponse();
$response->getBody()->write('hello world');
return $response;
}
}
One can easily run it by placing the following code in their web front-controller (e.g. public_html/index.php):
<?php
// web-root/front-controller.php
use Laminas\Diactoros\ResponseFactory;
use Practice\Sources\App\App;
require_once __DIR__.'/../vendor/autoload.php';
$responseFactory = new ResponseFactory();
$app = new App($responseFactory);
$app->run();
Now, I'd like to run Codeception feature tests against it, written in Gherkin. Consider the following simple test:
# tests/Feature/run.feature
# (also symlink'ed from tests/Codeception/tests/acceptance/ )
Feature: run app
Scenario: I run the app
When I am on page '/'
Then I see 'hello world'
To run acceptance tests against it, I have to provide my steps implementation. I'll reuse standard steps for that, provided by the Codeception:
<?php
// tests/Codeception/tests/_support/FeatureTester.php
namespace Practice\Tests\Codeception;
use Codeception\Actor;
abstract class FeatureTester extends Actor
{
// use _generated\AcceptanceTesterActions;
// use _generated\FunctionalTesterActions;
/**
* #When /^I am on page \'([^\']*)\'$/
*/
public function iAmOnPage($page)
{
$this->amOnPage($page);
}
/**
* #Then /^I see \'([^\']*)\'$/
*/
public function iSee($what)
{
$this->see($what);
}
}
<?php
// tests/Codeception/tests/_support/AcceptanceTester.php
namespace Practice\Tests\Codeception;
class AcceptanceTester extends FeatureTester
{
use _generated\AcceptanceTesterActions;
}
# tests/Codeception/codeception.yml
namespace: Practice\Tests\Codeception
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
actor_suffix: Tester
extensions:
enabled:
- Codeception\Extension\RunFailed
# tests/Codeception/tests/acceptance.suite.yml
actor: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\AcceptanceTester
But now, I want to use same tests code to run these same tests as functional tests using Codeception. For this, I have to enable the module that implements these same steps in functional way. Which one do I use? Codeception provides several, but they're for 3rd party frameworks, e.g. Laravel, Yii2, Symphony etc. What do I do for such a simple app that doesn't use any 3rd party framework?
Here's what I've managed to do. I've created my own \Codeception\Lib\InnerBrowser implementation that inherits from \Codeception\Module\PhpBrowser provided by Codeception, in which I substitute the web client that Codeception uses (it uses Guzzle) with my own implementation (which also inherits from the Guzzle client) that doesn't perform any web requests but requests my app instead:
# tests/Codeception/tests/functional.suite.yml
actor: FunctionalTester
modules:
enabled:
- \Practice\Tests\Codeception\Helper\CustomInnerBrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\FunctionalTester
<?php
// tests/Codeception/tests/_support/FunctionalTester.php
namespace Practice\Tests\Codeception;
class FunctionalTester extends FeatureTester
{
use _generated\FunctionalTesterActions;
}
In order for this to work, I have to make my app return Guzzle Responses (which implement PSR's ResponseInterface as well) - because PhpBrowser expects its web client to return them - which is why I had to make the ResponseFactory a constructor parameter to be able to substitute it in tests.
<?php
// tests/Codeception/tests/_support/Helper/CustomInnerBrowser.php
namespace Practice\Tests\Codeception\Helper;
use Codeception\Module\PhpBrowser;
use Http\Factory\Guzzle\ResponseFactory;
use Practice\Sources\App\App;
class CustomInnerBrowser extends PhpBrowser
{
private App $app;
public function __construct(...$args)
{
parent::__construct(...$args);
$responseFactory = new ResponseFactory();
$this->app = new App($responseFactory);
}
public function _prepareSession(): void
{
parent::_prepareSession();
$this->guzzle = new CustomInnerBrowserClient($this->guzzle->getConfig(), $this->app);
$this->client->setClient($this->guzzle);
}
}
<?php
// tests/Codeception/tests/_support/Helper/CustomInnerBrowserClient.php
namespace Practice\Tests\Codeception\Helper;
use GuzzleHttp\Client as GuzzleClient;
use Practice\Sources\App\App;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class CustomInnerBrowserClient extends GuzzleClient
{
private App $app;
public function __construct(array $config, App $app)
{
parent::__construct($config);
$this->app = $app;
}
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
return $this->app->handle($request);
}
}
In such a configuration, everything seems to work fine.
But there's a problem. Notice the App::handle() signature:
public function handle(RequestInterface $request): ResponseInterface
- it differs from the one that it implements, which is declared in RequestHandlerInterface:
public function handle(ServerRequestInterface $request): ResponseInterface;
Technically, it's completely legal because it doesn't break the parameter contravariance required by the Liskov Substitution Principle.
The problem that I've faced is that PhpBrowser assumes that it sends a (client-side) RequestInterfaces (over the network) but my app requires a (server-side) ServerRequestInterface instead, to be able to access parameters that are set on the server side such as ServerRequestInterface::getParsedBody(), session etc.
How do I workaround this? Framework modules provided by Codeception already do this somehow... And BTW, haven't Codeception (or someone else) provided yet an easy way to run functional tests against custom code?
Here's composer.json BTW:
{
"require": {
"php": "~7.4",
"laminas/laminas-diactoros": "^2.5",
"laminas/laminas-httphandlerrunner": "^1.3"
},
"require-dev": {
"codeception/codeception": "^4.1",
"codeception/module-phpbrowser": "^1.0.0",
"http-interop/http-factory-guzzle": "^1.0"
},
"autoload": {
"psr-4": {
"Practice\\Sources\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Practice\\Tests\\Unit\\": "tests/Unit/",
"Practice\\Tests\\Support\\": "tests/Support/",
"Practice\\Tests\\Codeception\\": "tests/Codeception/tests/_support/",
"Practice\\Tests\\Codeception\\_generated\\": "tests/Codeception/tests/_support/_generated/",
"Practice\\Tests\\Codeception\\Helper\\": "tests/Codeception/tests/_support/Helper/"
}
},
"scripts": {
"test-feature": "codecept run --config tests/Codeception/codeception.yml"
}
}
I am working on Yii2 advanced app and I have created cron job for my need but same code inside cron works in application but not works in console cron controller.
It gives error like 'Class PDO not found'.
namespace console\controllers;
use yii\console\Controller;
class CronsController extends Controller {
public function actionIndex($id = null) {
if(isset($id)){
$command = \Yii::$app->db->createCommand("INSERT INTO table (user) VALUES (:user)");
foreach($gets as $row){
$command->bindValue(':user', $row['user']);
$command->execute();
}
}
}
first step command run
php -m | grep PDO
To see if it exists,if not exists PDO, you need install PDO extension
http://php.net/manual/en/pdo.installation.php
I use the example LDAP Login with LDAP Server to connect my app with LDAP. I can connect the app example with the server. ALso I can modify the app and connect.
The problem is when I put the example in a Cordova Project for iOS when I put mfp as plugin.
I can see the app in _MobileBrowserSimulator and can connect to server but when I execute mfp cordoba emulate the app don't connect with server.
--- UPDATE ---
I use the same example that LDAP but this runs in mobile simulator but dont work in device. This is a simple example that run in simulator but not in device.
main.js
function getSecretData(){
var request = new WLResourceRequest("/Random/getInteger",
WLResourceRequest.GET);
request.send().then(onSuccess, onFailure);
}
function onSuccess(r) {
document.getElementById('guille').innerHTML = r;
}
function onFailure(r) {
document.getElementById('guille').innerHTML = r;
}
index.js
function wlCommonInit(){
WL.App.setServerUrl("http://127.0.0.1:10080/MyMFPProject",success, fail);
WL.Client.connect();
}
function success(r){
document.getElementById('guille').innerHTML = r;
}
function fail(r){
document.getElementById('guille').innerHTML = "error: " +r;
}
function onSuccess(r) {
document.getElementById('guille').innerHTML = JSON.stringify(r);
}
function onFailure(r) {
document.getElementById('guille').innerHTML = JSON.stringify(r);
}
Update: based on the comment, it sounds like ATS is still enabled, in which requests will indeed fail. Disable ATS by adding the following to the application's *-info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
Read more here: https://developer.ibm.com/mobilefirstplatform/2015/09/09/ats-and-bitcode-in-ios9/
The code looks fragile to me.
I would change it like below. If it will fail still then you need to create a test case demo of the failure. It sounds like it should fail also without anything related to LDAP...
function wlCommonInit(){
WL.App.setServerUrl("http://127.0.0.1:10080/MyMFPProject",success, fail);
}
function success(r){
document.getElementById('guille').innerHTML = r;
WL.Client.connect({onSuccess: connectSuccess, onFailure: connectFailure);
}
function connectSuccess() {
getSecretData();
// ...
}
function connectFailure() {
// handle connect failure
}
// the rest of the functions
What i did:
Instaled XAMPP (default settings)
Downloaded and instaled newest version of Yii (c:\xampp\htdocs\yii)
Made PATH (;c:\xampp\php)
I did everything what i should in CMD, after that localhost/yii/test is working but when i make new controller and i try go to site localhost/yii/test/bazadanych, then i have 404 error.
(c:\xampp\htdocs\yii\test\protected\controllers\BazadanychController.php)
<?php
class BazadanychController extends CController
{
public function actionIndex()
{
$Polaczenie = Yii::app()->db;
$Zapytanie = $Polaczenie->createCommand('CELECT * FROM osoby');
$DaneZBazy = $Zapytanie->query();
while(($Rekord=$DaneZBazy->read())!==false)
{
echo $Rekord['imie'].'<br>';
}
}
}
?>
Where did i mistake? I read polish book "yiiframework" (Author: Ćukasz Sosna, printed this year) and i made step by step so i don't know what is wrong. Any ideas?
Is there a way for me to check to see if someone is logged into Meteor from outside of Meteor; for example, from an Express.js application? I would like to know from the Express app who the currently logged in user is on a particular client so that if the API were called, we would know who to apply the results of the API call to.
So this is best done it two parts.
A method to check whether the user is online in meteor
You can probably do it with a meteor smart package (community package repo) : https://github.com/erundook/meteor-profile-online
Make sure you have meteorite, installed via npm install meteorite -g
In your package repo use : mrt add profile-online
Accessing meteor's data using Express
To access the stuff in Express you would need a DDP client, I know this one works with pre1 (The version of DDP with Meteor 0.57+): https://github.com/EventedMind/node-ddp-client
You can have a method that checks for you in meteor
Server js (Meteor)
Meteor.methods({
'isonline: function(id) {
return Meteor.users.find(id).profile.online;
}
}
Express:
var client = new DDPClient({
host: "localhost",
port: 3000
});
userid = '1' //The user _id of the person you want to check
client.connect(function () {
console.log("Connected to Meteor at localhost:3000");
client.call("isonline", [userid], function(err,result) {
client.close();
if(!err) {
if(result) {
console.log("User " + userid + " is online");
}
else
{
console.log("That user isn't online");
}
}
else
{
console.log(err)
}
});
});