Skip to content

Commit 11d3d28

Browse files
committed
initial commit
0 parents  commit 11d3d28

File tree

12 files changed

+573
-0
lines changed

12 files changed

+573
-0
lines changed

.codecov.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: auto
6+
threshold: 1%
7+
patch:
8+
default:
9+
target: auto
10+
threshold: 50%
11+
12+
comment: false
13+
github_checks:
14+
annotations: false

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.yml]
12+
indent_size = 2

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.github export-ignore
2+
/tests export-ignore
3+
phpunit.dist.xml export-ignore

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
schedule:
7+
- cron: '0 0 1,16 * *'
8+
9+
jobs:
10+
tests:
11+
name: PHP ${{ matrix.php }} - ${{ matrix.deps }}
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
php: [8.0, 8.1]
16+
deps: [hightest]
17+
include:
18+
- php: 8.0
19+
deps: lowest
20+
steps:
21+
- uses: zenstruck/.github@php-test-symfony
22+
with:
23+
php: ${{ matrix.php }}
24+
deps: ${{ matrix.deps }}
25+
26+
code-coverage:
27+
name: Code Coverage
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: zenstruck/.github@php-coverage-codecov
31+
with:
32+
php: 8.1
33+
34+
composer-validate:
35+
uses: zenstruck/.github/.github/workflows/php-composer-validate.yml@main
36+
37+
cs-check:
38+
uses: zenstruck/.github/.github/workflows/php-cs-fixer.yml@main
39+
with:
40+
php: 8.0
41+
42+
sca:
43+
uses: zenstruck/.github/.github/workflows/php-stan.yml@main
44+
with:
45+
php: 8.1

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/composer.lock
2+
/phpunit.xml
3+
/vendor/
4+
/build/
5+
/var/
6+
/.php-cs-fixer.cache
7+
/.phpunit.result.cache

.php-cs-fixer.dist.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
$finder = PhpCsFixer\Finder::create()
4+
->in([__DIR__.'/src', __DIR__.'/tests'])
5+
;
6+
$config = new PhpCsFixer\Config();
7+
8+
return $config
9+
->setRules([
10+
'@Symfony' => true,
11+
'@Symfony:risky' => true,
12+
'@DoctrineAnnotation' => true,
13+
'@PHP80Migration' => true,
14+
'@PHP80Migration:risky' => true,
15+
'@PHPUnit75Migration:risky' => true,
16+
'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'],
17+
'multiline_comment_opening_closing' => true,
18+
'array_syntax' => ['syntax' => 'short'],
19+
'ordered_imports' => [
20+
'imports_order' => ['const', 'class', 'function'],
21+
],
22+
'ordered_class_elements' => true,
23+
'native_function_invocation' => ['include' => ['@internal']],
24+
'explicit_indirect_variable' => true,
25+
'explicit_string_variable' => true,
26+
'escape_implicit_backslashes' => true,
27+
'mb_str_functions' => true,
28+
'logical_operators' => true,
29+
'php_unit_method_casing' => ['case' => 'snake_case'],
30+
'php_unit_test_annotation' => ['style' => 'annotation'],
31+
'no_unreachable_default_argument_value' => true,
32+
'declare_strict_types' => false,
33+
'void_return' => false,
34+
'single_trait_insert_per_statement' => false,
35+
'simple_to_complex_string_variable' => true,
36+
'no_superfluous_phpdoc_tags' => [
37+
'allow_mixed' => true,
38+
'allow_unused_params' => true,
39+
'remove_inheritdoc' => true,
40+
],
41+
'phpdoc_to_comment' => false,
42+
'function_declaration' => ['closure_function_spacing' => 'none', 'closure_fn_spacing' => 'none'],
43+
'nullable_type_declaration_for_default_null_value' => true,
44+
'phpdoc_types_order' => ['null_adjustment' => 'none', 'sort_algorithm' => 'none'],
45+
'phpdoc_separation' => ['groups' => [
46+
['test', 'dataProvider'],
47+
['template', 'implements', 'extends'],
48+
['phpstan-type', 'phpstan-import-type'],
49+
['deprecated', 'link', 'see', 'since'],
50+
['author', 'copyright', 'license', 'source'],
51+
['category', 'package', 'subpackage'],
52+
['property', 'property-read', 'property-write'],
53+
]],
54+
])
55+
->setRiskyAllowed(true)
56+
->setFinder($finder)
57+
;

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Kevin Bond
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

composer.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "zenstruck/temp-file",
3+
"description": "Temporary file wrapper.",
4+
"homepage": "https://github.com/zenstruck/temp-file",
5+
"type": "library",
6+
"license": "MIT",
7+
"keywords": [],
8+
"authors": [
9+
{
10+
"name": "Kevin Bond",
11+
"email": "kevinbond@gmail.com"
12+
}
13+
],
14+
"require": {
15+
"php": ">=8.0"
16+
},
17+
"require-dev": {
18+
"phpstan/phpstan": "^1.4",
19+
"phpunit/phpunit": "^9.5.0",
20+
"symfony/phpunit-bridge": "^6.1",
21+
"symfony/var-dumper": "^5.4|^6.0"
22+
},
23+
"config": {
24+
"preferred-install": "dist",
25+
"sort-packages": true
26+
},
27+
"autoload": {
28+
"psr-4": { "Zenstruck\\": ["src/"] }
29+
},
30+
"autoload-dev": {
31+
"psr-4": { "Zenstruck\\Tests\\": ["tests/"] }
32+
}
33+
}

phpstan.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
level: 8
3+
treatPhpDocTypesAsCertain: false
4+
paths:
5+
- src
6+
# ignoreErrors:
7+
# - '#no value type specified in iterable type array#'

phpunit.xml.dist

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
4+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
6+
bootstrap="vendor/autoload.php"
7+
colors="true"
8+
failOnRisky="true"
9+
failOnWarning="true"
10+
>
11+
<php>
12+
<ini name="error_reporting" value="-1" />
13+
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
14+
</php>
15+
16+
<testsuites>
17+
<testsuite name="zenstruck/temp-file Test Suite">
18+
<directory>tests</directory>
19+
</testsuite>
20+
</testsuites>
21+
22+
<coverage>
23+
<include>
24+
<directory>src</directory>
25+
</include>
26+
</coverage>
27+
28+
<listeners>
29+
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
30+
</listeners>
31+
</phpunit>

src/TempFile.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Zenstruck;
4+
5+
/**
6+
* @author Kevin Bond <kevinbond@gmail.com>
7+
*/
8+
final class TempFile extends \SplFileInfo
9+
{
10+
/** @var string[] */
11+
private static array $created = [];
12+
13+
public function __construct(?string $filename = null)
14+
{
15+
$filename ??= self::tempFile();
16+
17+
if (\is_dir($filename)) {
18+
throw new \LogicException("\"{$filename}\" is a directory.");
19+
}
20+
21+
parent::__construct($filename);
22+
23+
if (!self::$created) {
24+
// delete on script end
25+
\register_shutdown_function([self::class, 'purge']);
26+
}
27+
28+
self::$created[] = $filename;
29+
}
30+
31+
public static function new(?string $filename = null): self
32+
{
33+
return new self($filename);
34+
}
35+
36+
public static function withExtension(string $extension): self
37+
{
38+
$original = self::tempFile();
39+
40+
if (!@\rename($original, $new = "{$original}.{$extension}")) {
41+
throw new \RuntimeException('Unable to create temp file with extension.');
42+
}
43+
44+
return new self($new);
45+
}
46+
47+
/**
48+
* @param string|resource|\SplFileInfo $what
49+
*/
50+
public static function for(mixed $what, ?string $extension = null): self
51+
{
52+
$file = $extension ? self::withExtension($extension) : new self();
53+
54+
if ($what instanceof \SplFileInfo) {
55+
@\copy($what, $file) ?: throw new \RuntimeException('Unable to copy file.');
56+
57+
return $file->refresh();
58+
}
59+
60+
if (false === @\file_put_contents($file, $what)) {
61+
throw new \RuntimeException('Unable to write to file.');
62+
}
63+
64+
return $file->refresh();
65+
}
66+
67+
/**
68+
* Create temporary image file.
69+
*
70+
* @source https://github.com/laravel/framework/blob/183d38f18c0ea9fe13b6d10a6d8360be881d096c/src/Illuminate/Http/Testing/FileFactory.php#L68
71+
*/
72+
public static function image(int $width = 10, int $height = 10, string $type = 'jpg'): self
73+
{
74+
$type = \mb_strtolower($type);
75+
$file = self::withExtension($type);
76+
77+
if (false === $image = @\imagecreatetruecolor($width, $height)) {
78+
throw new \RuntimeException('Error creating temporary image.');
79+
}
80+
81+
$ret = match ($type) {
82+
'jpeg', 'jpg' => @\imagejpeg($image, (string) $file),
83+
'png' => @\imagepng($image, (string) $file),
84+
'gif' => @\imagegif($image, (string) $file),
85+
'bmp' => @\imagebmp($image, (string) $file),
86+
'webp' => @\imagewebp($image, (string) $file),
87+
'wbmp' => @\imagewbmp($image, (string) $file),
88+
default => throw new \InvalidArgumentException(\sprintf('"%s" is an invalid image type.', $type)),
89+
};
90+
91+
if (false === $ret) {
92+
throw new \RuntimeException('Error creating temporary image.');
93+
}
94+
95+
return $file->refresh();
96+
}
97+
98+
/**
99+
* Manually delete all created temp files. Useful for long-running
100+
* processes.
101+
*/
102+
public static function purge(): void
103+
{
104+
foreach (self::$created as $filename) {
105+
if (\file_exists($filename)) {
106+
\unlink($filename);
107+
}
108+
}
109+
110+
self::$created = [];
111+
}
112+
113+
public function contents(): string
114+
{
115+
return \file_get_contents($this) ?: throw new \RuntimeException('Unable to get file contents.');
116+
}
117+
118+
public function refresh(): self
119+
{
120+
\clearstatcache(false, $this);
121+
122+
return $this;
123+
}
124+
125+
public function delete(): self
126+
{
127+
if (\file_exists($this)) {
128+
\unlink($this);
129+
}
130+
131+
return $this;
132+
}
133+
134+
private static function tempFile(): string
135+
{
136+
if (false === $filename = @\tempnam(\sys_get_temp_dir(), 'zstf_')) {
137+
throw new \RuntimeException('Failed to create temporary file.');
138+
}
139+
140+
return $filename;
141+
}
142+
}

0 commit comments

Comments
 (0)