Unverified Commit e0566f9d authored by Graham Campbell's avatar Graham Campbell Committed by GitHub
Browse files

Enabled StyleCI (#218)

parent b1a505d3
......@@ -4,8 +4,8 @@
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.styleci.yml export-ignore
CHANGELOG.md export-ignore
phpcs.xml.dist export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
README.md export-ignore
......
......@@ -5,38 +5,6 @@ on:
pull_request:
jobs:
phpcs:
name: PHP CodeSniffer
runs-on: ubuntu-20.04
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
tools: composer:v2
coverage: none
- name: Install Dependencies
uses: nick-invision/retry@v1
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --no-interaction --no-progress
- name: Install PHP CodeSniffer
uses: nick-invision/retry@v1
with:
timeout_minutes: 5
max_attempts: 5
command: composer bin phpcs update --no-interaction --no-progress
- name: Execute PHP CodeSniffer
run: vendor/bin/phpcs
phpstan:
name: PHPStan
runs-on: ubuntu-20.04
......
.phpunit.result.cache
composer.lock
phpcs.xml
phpstan.neon
phpunit.xml
vendor
preset: symfony
risky: true
enabled:
- align_phpdoc
- alpha_ordered_imports
- array_indentation
- const_visibility_required
- native_constant_invocation
- native_function_invocation
- phpdoc_order
- void_return
disabled:
- native_constant_invocation_symfony
- native_function_invocation_symfony
- no_superfluous_phpdoc_tags_symfony
- phpdoc_to_comment
- phpdoc_var_without_name
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__.'/../vendor/autoload.php';
use HeadlessChromium\BrowserFactory;
......@@ -10,7 +10,7 @@ $browser = $factory->createBrowser();
// navigate to a page with a form
$page = $browser->createPage();
$page->navigate('file://' . __DIR__ . '/html/form.html')->waitForNavigation();
$page->navigate('file://'.__DIR__.'/html/form.html')->waitForNavigation();
// put 'hello' in the input and submit the form
$evaluation = $page->evaluate(
......@@ -26,4 +26,4 @@ $evaluation->waitForPageReload();
// get value in the new page
$value = $page->evaluate('document.querySelector("#value").innerHTML')->getReturnValue();
var_dump($value);
\var_dump($value);
......@@ -10,7 +10,7 @@
* of creating a new one. If chrome was closed or crashed, a new instance is started again.
*/
require(__DIR__ . '/../vendor/autoload.php');
require __DIR__.'/../vendor/autoload.php';
// path to the file to store websocket's uri
$socketFile = '/tmp/chrome-php-demo-socket';
......@@ -19,12 +19,12 @@ $socketFile = '/tmp/chrome-php-demo-socket';
$browser = null;
// try to connect to chrome instance if it exists
if (file_exists($socketFile)) {
$socket = file_get_contents($socketFile);
if (\file_exists($socketFile)) {
$socket = \file_get_contents($socketFile);
try {
$browser = \HeadlessChromium\BrowserFactory::connectToBrowser($socket, [
'debugLogger' => 'php://stdout'
'debugLogger' => 'php://stdout',
]);
} catch (\HeadlessChromium\Exception\BrowserConnectionFailed $e) {
// The browser was probably closed
......@@ -37,14 +37,14 @@ if (!$browser) {
$factory = new \HeadlessChromium\BrowserFactory();
$browser = $factory->createBrowser([
'headless' => false,
'keepAlive' => true
'keepAlive' => true,
]);
// save the uri to be able to connect again to browser
file_put_contents($socketFile, $browser->getSocketUri());
\file_put_contents($socketFile, $browser->getSocketUri());
}
// do something with the browser
$page = $browser->createPage();
$page->navigate('http://example.com')->waitForNavigation();
\ No newline at end of file
$page->navigate('http://example.com')->waitForNavigation();
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="gsouf">
<file>./src/</file>
<file>./tests/</file>
<!--The complete PSR-12 ruleset-->
<rule ref="PSR12"/>
<!-- Arrays -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
<!-- Long lines do no apply on test files -->
<rule ref="Generic.Files.LineLength.TooLong">
<exclude-pattern>./tests/*</exclude-pattern>
</rule>
<!-- When test method have weird signatures -->
<rule ref="PEAR.Functions.ValidDefaultValue.NotAtEnd">
<exclude-pattern>./tests/*</exclude-pattern>
</rule>
<!-- By default we use single quote only. Double quotes come when a variable is inside -->
<rule ref="Squiz.Strings.DoubleQuoteUsage">
<exclude name="Squiz.Strings.DoubleQuoteUsage.ContainsVar"/>
</rule>
<!-- Disallow some function like var_dump -->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array" value="dump=>null,var_dump=>null,sizeof=>count,delete=>unset,print=>echo"/>
</properties>
</rule>
</ruleset>
......@@ -15,7 +15,7 @@ class AutoDiscover
{
public function getChromeBinaryPath(): string
{
if (array_key_exists('CHROME_PATH', $_SERVER)) {
if (\array_key_exists('CHROME_PATH', $_SERVER)) {
return $_SERVER['CHROME_PATH'];
}
......@@ -37,11 +37,11 @@ class AutoDiscover
{
try {
// accessing the registry can be costly, but this specific key is likely to be already cached in memory
$registryKey = shell_exec(
$registryKey = \shell_exec(
'reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe" /ve'
);
preg_match('/.:(?!.*:).*/', $registryKey, $matches);
\preg_match('/.:(?!.*:).*/', $registryKey, $matches);
return $matches[0];
} catch (\Throwable $e) {
......@@ -52,6 +52,6 @@ class AutoDiscover
public function getOS(): string
{
return PHP_OS;
return \PHP_OS;
}
}
......@@ -15,8 +15,8 @@ use HeadlessChromium\Communication\Connection;
use HeadlessChromium\Communication\Message;
use HeadlessChromium\Communication\Target;
use HeadlessChromium\Exception\CommunicationException;
use HeadlessChromium\Exception\NoResponseAvailable;
use HeadlessChromium\Exception\CommunicationException\ResponseHasError;
use HeadlessChromium\Exception\NoResponseAvailable;
use HeadlessChromium\Exception\OperationTimedOut;
class Browser
......@@ -32,7 +32,8 @@ class Browser
protected $targets = [];
/**
* A preScript to be automatically added on every new pages
* A preScript to be automatically added on every new pages.
*
* @var string|null
*/
protected $pagePreScript;
......@@ -42,15 +43,13 @@ class Browser
$this->connection = $connection;
// listen for target created
$this->connection->on(Connection::EVENT_TARGET_CREATED, function (array $params) {
$this->connection->on(Connection::EVENT_TARGET_CREATED, function (array $params): void {
// create and store the target
$this->targets[$params['targetInfo']['targetId']] = new Target($params['targetInfo'], $this->connection);
});
// listen for target info changed
$this->connection->on(Connection::EVENT_TARGET_INFO_CHANGED, function (array $params) {
$this->connection->on(Connection::EVENT_TARGET_INFO_CHANGED, function (array $params): void {
// get target by id
$target = $this->getTarget($params['targetInfo']['targetId']);
......@@ -60,8 +59,7 @@ class Browser
});
// listen for target destroyed
$this->connection->on(Connection::EVENT_TARGET_DESTROYED, function (array $params) {
$this->connection->on(Connection::EVENT_TARGET_DESTROYED, function (array $params): void {
// get target by id
$target = $this->getTarget($params['targetId']);
......@@ -71,7 +69,7 @@ class Browser
$target->destroy();
$this->connection
->getLogger()
->debug('✘ target(' . $params['targetId'] . ') was destroyed and unreferenced.');
->debug('✘ target('.$params['targetId'].') was destroyed and unreferenced.');
}
});
......@@ -93,26 +91,27 @@ class Browser
*
* @param string|null $script
*/
public function setPagePreScript(string $script = null)
public function setPagePreScript(string $script = null): void
{
$this->pagePreScript = $script;
}
/**
* Closes the browser
* Closes the browser.
*
* @throws \Exception
*/
public function close()
public function close(): void
{
$this->sendCloseMessage();
}
/**
* Send close message to the browser
* Send close message to the browser.
*
* @throws OperationTimedOut
*/
final public function sendCloseMessage()
final public function sendCloseMessage(): void
{
$r = $this->connection->sendMessageSync(new Message('Browser.close'));
if (!$r->isSuccessful()) {
......@@ -123,15 +122,16 @@ class Browser
}
/**
* Creates a new page
* Creates a new page.
*
* @throws NoResponseAvailable
* @throws CommunicationException
* @throws OperationTimedOut
*
* @return Page
*/
public function createPage(): Page
{
// page url
$params = ['url' => 'about:blank'];
......@@ -179,14 +179,16 @@ class Browser
/**
* @param string $targetId
*
* @return Target|null
*/
public function getTarget($targetId)
{
// make sure target was created (via Target.targetCreated event)
if (!array_key_exists($targetId, $this->targets)) {
if (!\array_key_exists($targetId, $this->targets)) {
return null;
}
return $this->targets[$targetId];
}
......@@ -195,6 +197,6 @@ class Browser
*/
public function getTargets()
{
return array_values($this->targets);
return \array_values($this->targets);
}
}
......@@ -13,7 +13,6 @@ namespace HeadlessChromium\Browser;
use HeadlessChromium\Browser;
use HeadlessChromium\Communication\Connection;
use HeadlessChromium\Communication\Message;
use HeadlessChromium\Exception\OperationTimedOut;
use HeadlessChromium\Utils;
use Psr\Log\LoggerAwareInterface;
......@@ -25,14 +24,15 @@ use Symfony\Component\Process\Process;
use Wrench\Exception\SocketException;
/**
* A browser process starter. Don't use directly, use BrowserFactory instead
* A browser process starter. Don't use directly, use BrowserFactory instead.
*/
class BrowserProcess implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* chrome instance's user data data
* chrome instance's user data data.
*
* @var string
*/
protected $userDataDir;
......@@ -43,7 +43,8 @@ class BrowserProcess implements LoggerAwareInterface
protected $process;
/**
* True if the user data dir is temporary and should be deleted on process closes
* True if the user data dir is temporary and should be deleted on process closes.
*
* @var bool
*/
protected $userDataDirIsTemp;
......@@ -75,21 +76,22 @@ class BrowserProcess implements LoggerAwareInterface
/**
* BrowserProcess constructor.
*
* @param LoggerInterface|null $logger
*/
public function __construct(LoggerInterface $logger = null)
{
// set or create logger
$this->setLogger($logger ?? new NullLogger());
}
/**
* Starts the browser
* Starts the browser.
*
* @param string $binary
* @param array $options
*/
public function start($binary, $options)
public function start($binary, $options): void
{
if ($this->wasStarted) {
// cannot start twice because once started this class contains the necessary data to cleanup the browser.
......@@ -103,7 +105,7 @@ class BrowserProcess implements LoggerAwareInterface
$this->logger->debug('process: initializing');
// user data dir
if (!array_key_exists('userDataDir', $options) || !$options['userDataDir']) {
if (!\array_key_exists('userDataDir', $options) || !$options['userDataDir']) {
// if no data dir specified create it
$options['userDataDir'] = $this->createTempDir();
......@@ -113,13 +115,13 @@ class BrowserProcess implements LoggerAwareInterface
$this->userDataDir = $options['userDataDir'];
// log
$this->logger->debug('process: using directory: ' . $options['userDataDir']);
$this->logger->debug('process: using directory: '.$options['userDataDir']);
// get args for command line
$args = $this->getArgsFromOptions($binary, $options);
// setup chrome process
if (!array_key_exists('keepAlive', $options) || !$options['keepAlive']) {
if (!\array_key_exists('keepAlive', $options) || !$options['keepAlive']) {
$process = new Process($args);
} else {
$process = new ProcessKeepAlive($args);
......@@ -127,7 +129,7 @@ class BrowserProcess implements LoggerAwareInterface
$this->process = $process;
// log
$this->logger->debug('process: starting process: ' . $process->getCommandLine());
$this->logger->debug('process: starting process: '.$process->getCommandLine());
// and start
$process->start();
......@@ -137,14 +139,14 @@ class BrowserProcess implements LoggerAwareInterface
$this->wsUri = $this->waitForStartup($process, $startupTimeout * 1000 * 1000);
// log
$this->logger->debug('process: connecting using ' . $this->wsUri);
$this->logger->debug('process: connecting using '.$this->wsUri);
// connect to browser
$connection = new Connection($this->wsUri, $this->logger, $options['sendSyncDefaultTimeout'] ?? 5000);
$connection->connect();
// connection delay
if (array_key_exists('connectionDelay', $options)) {
if (\array_key_exists('connectionDelay', $options)) {
$connection->setConnectionDelay($options['connectionDelay']);
}
......@@ -172,18 +174,19 @@ class BrowserProcess implements LoggerAwareInterface
}
/**
* Kills the process and clean temporary files
* Kills the process and clean temporary files.
*
* @throws OperationTimedOut
*/
public function kill()
public function kill(): void
{
// log
$this->logger->debug('process: killing chrome');
if ($this->wasKilled) {
// log
$this->logger->debug('process: chrome already killed, ignoring');
return;
}
......@@ -249,7 +252,7 @@ class BrowserProcess implements LoggerAwareInterface
$exitCode = $this->process->stop();
// log
$this->logger->debug('process: process stopped with exit code ' . $exitCode);
$this->logger->debug('process: process stopped with exit code '.$exitCode);
}
}
......@@ -257,7 +260,7 @@ class BrowserProcess implements LoggerAwareInterface
if ($this->userDataDirIsTemp && $this->userDataDir) {
try {
// log
$this->logger->debug('process: cleaning temporary resources:' . $this->userDataDir);
$this->logger->debug('process: cleaning temporary resources:'.$this->userDataDir);
// cleaning
$fs = new Filesystem();
......@@ -270,8 +273,10 @@ class BrowserProcess implements LoggerAwareInterface
}
/**
* Get args for creating chrome's startup command
* Get args for creating chrome's startup command.
*
* @param array $options
*
* @return array
*/
private function getArgsFromOptions($binary, array $options)
......@@ -308,7 +313,7 @@ class BrowserProcess implements LoggerAwareInterface
];
// enable headless mode
if (!array_key_exists('headless', $options) || $options['headless']) {
if (!\array_key_exists('headless', $options) || $options['headless']) {
$args[] = '--headless';
$args[] = '--disable-gpu';
$args[] = '--font-render-hinting=none';
......@@ -317,62 +322,62 @@ class BrowserProcess implements LoggerAwareInterface
}
// disable loading of images (currently can't be done via devtools, only CLI)
if (array_key_exists('enableImages', $options) && ($options['enableImages'] === false)) {
if (\array_key_exists('enableImages', $options) && (false === $options['enableImages'])) {
$args[] = '--blink-settings=imagesEnabled=false';
}
// window's size
if (array_key_exists('windowSize', $options) && $options['windowSize']) {
if (\array_key_exists('windowSize', $options) && $options['windowSize']) {
if (
!is_array($options['windowSize']) ||
count($options['windowSize']) !== 2 ||
!is_numeric($options['windowSize'][0]) ||
!is_numeric($options['windowSize'][1])
!\is_array($options['windowSize']) ||
2 !== \count($options['windowSize']) ||
!\is_numeric($options['windowSize'][0]) ||
!\is_numeric($options['windowSize'][1])
) {
throw new \InvalidArgumentException(
'Option "windowSize" must be an array of dimensions (eg: [1000, 1200])'
);
throw new \InvalidArgumentException('Option "windowSize" must be an array of dimensions (eg: [1000, 1200])');
}
$args[] = '--window-size=' . implode(',', $options['windowSize']) ;
$args[] = '--window-size='.\implode(',', $options['windowSize']);
}
// sandbox mode - useful if you want to use chrome headless inside docker
if (array_key_exists('noSandbox', $options) && $options['noSandbox']) {
if (\array_key_exists('noSandbox', $options) && $options['noSandbox']) {
$args[] = '--no-sandbox';
}
// user agent
if (array_key_exists('userAgent', $options)) {
$args[] = '--user-agent=' . $options['userAgent'];
if (\array_key_exists('userAgent', $options)) {
$args[] = '--user-agent='.$options['userAgent'];
}
// ignore certificate errors
if (array_key_exists('ignoreCertificateErrors', $options) && $options['ignoreCertificateErrors']) {
if (\array_key_exists('ignoreCertificateErrors', $options) && $options['ignoreCertificateErrors']) {
$args[] = '--ignore-certificate-errors';
}
// add custom flags
if (array_key_exists('customFlags', $options) && is_array($options['customFlags'])) {
$args = array_merge($args, $options['customFlags']);
if (\array_key_exists('customFlags', $options) && \is_array($options['customFlags'])) {
$args = \array_merge($args, $options['customFlags']);
}
// add user data dir to args
$args[] = '--user-data-dir=' . $options['userDataDir'];
$args[] = '--user-data-dir='.$options['userDataDir'];
return $args;
}
/**
* Wait for chrome to startup (given a process) and return the ws uri to connect to
* Wait for chrome to startup (given a process) and return the ws uri to connect to.
*
* @param Process $process
* @param int $timeout
* @param int $timeout
*
* @return mixed
*/
private function waitForStartup(Process $process, int $timeout)
{
// log
$this->logger->debug('process: waiting for ' . $timeout / 1000000 . ' seconds for startup');
$this->logger->debug('process: waiting for '.$timeout / 1000000 .' seconds for startup');
try {
$generator = function (Process $process) {
......@@ -383,23 +388,23 @@ class BrowserProcess implements LoggerAwareInterface
// exception
$message = 'Chrome process stopped before startup completed.';
$error = trim($process->getErrorOutput());
$error = \trim($process->getErrorOutput());
if (!empty($error)) {
$message .= ' Additional info: ' . $error;
$message .= ' Additional info: '.$error;
}
throw new \RuntimeException($message);
}
$output = trim($process->getIncrementalErrorOutput());
$output = \trim($process->getIncrementalErrorOutput());
if ($output) {
// log
$this->logger->debug('process: chrome output:' . $output);
$this->logger->debug('process: chrome output:'.$output);
$outputs = explode(PHP_EOL, $output);
$outputs = \explode(\PHP_EOL, $output);
foreach ($outputs as $output) {
$output = trim($output);
$output = \trim($output);
// ignore empty line
if (empty($output)) {
......@@ -407,13 +412,14 @@ class BrowserProcess implements LoggerAwareInterface