Skip to content

Comments

CLI command to generate a report#10

Merged
makomweb merged 27 commits intomasterfrom
next
Jan 25, 2026
Merged

CLI command to generate a report#10
makomweb merged 27 commits intomasterfrom
next

Conversation

@makomweb
Copy link
Owner

@makomweb makomweb commented Jan 19, 2026

  • CLI command to generate a report for how classes are distributed and whether there are violations
grafik

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a CLI command to generate an HTML report showing how classes are distributed across DDD categories (aggregate roots, entities, value objects, etc.) and whether there are forbidden relations between them.

Changes:

  • Adds Symfony Console command tactix:report to generate static HTML reports with interactive charts
  • Integrates Symfony Bundle (TactixBundle) with dependency injection support for use in Symfony applications
  • Provides static HTML/CSS/JavaScript report templates with ECharts visualization

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
src/TactixBundle.php New Symfony bundle class for integration with Symfony applications
src/DependencyInjection/TactixExtension.php Symfony DI extension to load services configuration
src/DependencyInjection/Configuration.php Symfony configuration tree builder (placeholder configuration)
src/Command/ReportCommand.php Main CLI command that analyzes classes and generates HTML reports
src/Command/Report.php Data class to categorize classes by DDD attributes
resources/report/*.{html,js,css} Static templates for the HTML report with charts and tables
resources/config/services.yaml Symfony service configuration for the ReportCommand
src/IgnoreableTypes.php Removed typed constant declaration (PHP 8.3+ feature) for PHP 8.2 compatibility
src/Analyzer/Class/Name.php Removed typed constant declaration (PHP 8.3+ feature) for PHP 8.2 compatibility
docker/php/Dockerfile Downgraded from PHP 8.4 to PHP 8.2
composer.json Added Symfony dependencies and adjusted PHPUnit version
README.md Added documentation for the new report command and fixed spelling
AGENTS.md Updated with new project structure and command information
Makefile Updated container commands to use compose exec instead of run

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1 @@
const reportData = {"folder":"src/TBone/","classes":{"aggregate_roots":["App\\TBone\\Depot\\Depot","App\\TBone\\Depot\\WatchList"],"entities":["App\\TBone\\Index\\iShares\\StoxxEurope600","App\\TBone\\Index\\Spider\\SP500","App\\TBone\\Index\\Yahoo\\YahooIndex","App\\TBone\\Index\\Yahoo\\Component"],"factories":["App\\TBone\\Depot\\PositionsFactory","App\\TBone\\Index\\iShares\\SectorNameFactory","App\\TBone\\Index\\iShares\\iShares","App\\TBone\\Index\\iShares\\HoldingFactory","App\\TBone\\Index\\Spider\\SectorNameFactory","App\\TBone\\Index\\Spider\\HoldingFactory","App\\TBone\\Index\\Spider\\SectorIdFactory","App\\TBone\\ConfirmationFactory"],"repositories":[],"services":["App\\TBone\\Depot\\Positions","App\\TBone\\Depot\\Invested","App\\TBone\\Depot\\AmountCalculator","App\\TBone\\Depot\\Gain","App\\TBone\\Depot\\PurchaseCalculator","App\\TBone\\Instrumentation\\Tracer","App\\TBone\\DistinctUntilChanged","App\\TBone\\Index\\HoldingComparer","App\\TBone\\Index\\Spider\\Spider","App\\TBone\\Index\\Spider\\SectorWeight","App\\TBone\\Index\\Yahoo\\FloatComparer","App\\TBone\\Index\\Yahoo\\ConfirmationDtoComparer","App\\TBone\\Index\\Yahoo\\TimestampComparer","App\\TBone\\Index\\Yahoo\\PerformanceProvider","App\\TBone\\Index\\Yahoo\\Currency","App\\TBone\\Index\\Yahoo\\AddressProvider"],"value_objects":["App\\TBone\\InitialAverage","App\\TBone\\Average","App\\TBone\\Depot\\Watch","App\\TBone\\Depot\\Adjustment","App\\TBone\\Depot\\DepotEvent","App\\TBone\\Depot\\Position","App\\TBone\\Price","App\\TBone\\Depot\\Cash","App\\TBone\\Depot\\Risk","App\\TBone\\Depot\\StopLoss","App\\TBone\\Depot\\TakeProfit","App\\TBone\\TrueRange","App\\TBone\\Depot\\Resume","App\\TBone\\Depot\\Performance","App\\TBone\\Candle","App\\TBone\\LinearRegression","App\\TBone\\TimeRange","App\\TBone\\Confirmation","App\\TBone\\MovingAverages","App\\TBone\\Indicates","App\\TBone\\MovingAverage","App\\TBone\\Index\\Holding","App\\TBone\\Index\\Sector","App\\TBone\\Index\\FinanzenNet\\Share","App\\TBone\\Index\\UserAgent","App\\TBone\\Index\\Yahoo\\SymbolLookupResult","App\\TBone\\Index\\Yahoo\\ConfirmationDto","App\\TBone\\Index\\Yahoo\\CandleRange","App\\TBone\\Performance","App\\TBone\\Index\\Yahoo\\Quote","App\\TBone\\Index\\Yahoo\\YahooPrice","App\\TBone\\Index\\Yahoo\\HistoryDocument\\TradingPeriod","App\\TBone\\Index\\Yahoo\\HistoryDocument\\DTO","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Quote","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Indicators","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Meta","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Result","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Chart","App\\TBone\\Index\\Yahoo\\HistoryDocument\\AdjustedClose","App\\TBone\\Index\\Yahoo\\HistoryDocument\\Period","App\\TBone\\Slope"],"interfaces":["App\\TBone\\Depot\\PositionsInterface","App\\TBone\\Depot\\DepotEventStoreInterface","App\\TBone\\CurrentUserInterface","App\\TBone\\Instrumentation\\LoggingInterface","App\\TBone\\Depot\\QuoteFetcherInterface","App\\TBone\\CacheInterface","App\\TBone\\Instrumentation\\TraceInterface","App\\TBone\\Instrumentation\\TracingInterface","App\\TBone\\Instrumentation\\MetricsInterface","App\\TBone\\Index\\IndexInterface","App\\TBone\\Index\\Yahoo\\DocumentProviderInterface","App\\TBone\\DenormalizerInterface","App\\TBone\\NormalizerInterface"],"exceptions":["App\\TBone\\NotEnoughCandlesException","App\\TBone\\TBoneException","App\\TBone\\Depot\\CancelledException","App\\TBone\\Depot\\InsufficientFundsException","InvalidArgumentException","App\\TBone\\Depot\\DepotException","OutOfBoundsException","LogicException","App\\TBone\\Index\\Yahoo\\HistoryDocument\\InvalidDocumentException","App\\TBone\\Index\\Yahoo\\YahooException","App\\TBone\\NoConfirmationsException","App\\TBone\\TimeRangeException"],"uncategorized":["App\\TBone\\TriggerIndicator","App\\TBone\\CandleColor","App\\TBone\\Depot\\SubjectType","App\\TBone\\Depot\\DepotEventType","App\\TBone\\Depot\\SuggestionType","App\\TBone\\ConfirmationIndicator","App\\TBone\\TrendIndicator","App\\TBone\\TimeRangeEnum","App\\TBone\\Frequency","App\\TBone\\Index\\SortOrder","App\\TBone\\Index\\SectorName","App\\TBone\\Index\\iShares\\iSharesHoldingCsvColumn","App\\TBone\\Index\\Spider\\SpiderHoldingCsvColumn","App\\TBone\\Index\\FinanzenNet\\IndexCode","App\\TBone\\Index\\Yahoo\\IndexName","App\\TBone\\Index\\Yahoo\\SortOrder","App\\TBone\\Index\\Yahoo\\FloatComparerResult","App\\TBone\\Index\\Yahoo\\YahooPriceType"]},"forbidden":[]} No newline at end of file
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The report.js file contains hardcoded example data from "src/TBone/" which appears to be from a specific project. This file should either be:

  1. Excluded from the repository (added to .gitignore) since it will be generated by the command
  2. Or contain placeholder/example data that is clearly marked as a template

Having project-specific data committed to the repository is confusing and could mislead users about what this file should contain.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 219 to 348
file_put_contents($destination, 'const reportData = '.$json.';');

// Ensure static assets (chart.js, styles.css) are available in the report directory.
// Prefer packaged templates under resources/report, otherwise create sensible defaults.
$resourceDir = __DIR__.'/../../resources/report';

// chart.js
if (is_file($resourceDir.'/chart.js')) {
copy($resourceDir.'/chart.js', $dir.'/chart.js');
} else {
// default minimal chart script inspired by tbone's chart.js using ECharts
$defaultChartJs = <<<'JS'
// Basic chart renderer expecting global reportData and echarts
document.addEventListener("DOMContentLoaded", function () {
const { classes, forbidden, folder } = reportData;
document.getElementById("reportFolder").textContent = `Report for ${folder}`;

const colors = [
"#3498db", "#e74c3c", "#1abc9c", "#95a5a6", "#2ecc71", "#f39c12", "#f1c40f", "#9b59b6", "#7f8c8d"
];

const labels = Object.keys(classes).filter(key => Array.isArray(classes[key]));
const values = labels.map(key => classes[key].length);
const readableLabels = labels.map(key => key.replace(/_/g, ' '));

const pieEl = document.getElementById('pieChart');
const barEl = document.getElementById('barChart');
if (pieEl && barEl && window.echarts) {
const pieChart = echarts.init(pieEl);
const pieData = readableLabels.map((label, index) => ({ name: label, value: values[index] }));
pieChart.setOption({
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left' },
series: [{ name: 'Classes', type: 'pie', radius: '50%', data: pieData, color: colors }]
});

const barChart = echarts.init(barEl);
barChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
xAxis: { type: 'category', data: readableLabels, axisLabel: { rotate: 45 } },
yAxis: { type: 'value', minInterval: 1 },
series: [{ name: 'Count', type: 'bar', data: values, itemStyle: { color: params => colors[params.dataIndex % colors.length] } }]
});

window.addEventListener('resize', () => { pieChart.resize(); barChart.resize(); });
}

// populate classes table if present
const container = document.getElementById('classesTableContainer');
if (container) {
const all = [];
labels.forEach(category => {
classes[category].forEach(c => all.push({ name: c, category }));
});
all.sort((a,b) => a.name.localeCompare(b.name));
const table = document.createElement('table');
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
['Class','Category'].forEach(h => {
const th = document.createElement('th'); th.textContent = h; th.style.border = '1px solid #ddd'; th.style.padding = '8px'; th.style.background = '#f5f5f5'; headerRow.appendChild(th);
});
thead.appendChild(headerRow); table.appendChild(thead);
const tbody = document.createElement('tbody');
all.forEach((item, idx) => {
const tr = document.createElement('tr'); if (idx%2===0) tr.style.background='#f9f9f9';
const tdName = document.createElement('td'); tdName.textContent = item.name; tdName.style.border='1px solid #ddd'; tdName.style.padding='8px';
const tdCat = document.createElement('td'); tdCat.textContent = item.category; tdCat.style.border='1px solid #ddd'; tdCat.style.padding='8px';
tr.appendChild(tdName); tr.appendChild(tdCat); tbody.appendChild(tr);
});
table.appendChild(tbody); container.appendChild(table);
}
});
JS;
file_put_contents($dir.'/chart.js', $defaultChartJs);
}

// styles.css
if (is_file($resourceDir.'/styles.css')) {
copy($resourceDir.'/styles.css', $dir.'/styles.css');
} else {
$defaultCss = <<<'CSS'
body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; margin: 16px; }
h1 { text-align: center; }
.chart-row { display:flex; gap:20px; justify-content:center; margin:20px 0; }
.chart-container { border:1px solid #ddd; border-radius:8px; padding:6px; }
#classesTableContainer { max-width: 1000px; margin: 0 auto; }
CSS;
file_put_contents($dir.'/styles.css', $defaultCss);
}

// index.html referencing the static assets. Use ECharts CDN and include chart.js and report.js
$indexHtml = <<<'HTML'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Charts Report</title>

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
<script src="report.js"></script>
</head>
<body>
<h1 id="reportFolder" style="text-align: center;"></h1>

<h2 style="text-align: center;">Class distribution</h2>

<div class="chart-row" style="display: flex; gap: 20px; justify-content: center; margin: 20px 0;">
<div class="chart-container" style="width: 45%; height: 400px; border: 1px solid #ddd; border-radius: 8px;">
<div id="pieChart" style="width: 100%; height: 100%;"></div>
</div>
<div class="chart-container" style="width: 45%; height: 400px; border: 1px solid #ddd; border-radius: 8px;">
<div id="barChart" style="width: 100%; height: 100%;"></div>
</div>
</div>

<h2 style="text-align: center; margin-top: 40px;">All classes</h2>
<div id="classesTableContainer"></div>

<script src="chart.js"></script>
</body>
</html>
HTML;

file_put_contents($dir.'/index.html', $indexHtml);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file_put_contents, copy, and mkdir operations don't check for failures. These operations can fail due to permissions, disk space, or other I/O issues. Consider checking the return values and throwing meaningful exceptions when operations fail, so users get clear error messages instead of silent failures or PHP warnings.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 86 to 101
shouldNotStartWith: [
'App\\Kernel',
'App\\CLI\\',
'App\\DDD\\',
'Doctrine\\',
'Symfony\\',
'Psr\\',
'PhpParser\\',
'phpDocumentor\\',
'Monolog\\',
'OpenTelemetry\\',
'Rx\\',
'React\\',
'InfluxDB2\\',
'EasyCorp\\Bundle\\EasyAdminBundle\\',
],
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded list of namespaces to filter (App\Kernel, App\CLI, App\DDD, etc.) appears to be specific to a particular project rather than being general-purpose. For a library that can be reused across different projects, these filters should be:

  1. Configurable through command options (e.g., --exclude-namespace)
  2. Or moved to a configuration file
  3. Or at minimum, documented as example filters that users should customize

This makes the command less reusable without modification.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback


document.addEventListener("DOMContentLoaded", function () {

const { classes, forbidden, folder } = reportData;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable forbidden.

Suggested change
const { classes, forbidden, folder } = reportData;
const { classes, folder } = reportData;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Jan 25, 2026

@makomweb I've opened a new pull request, #11, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI commented Jan 25, 2026

@makomweb I've opened a new pull request, #12, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI commented Jan 25, 2026

@makomweb I've opened a new pull request, #13, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits January 25, 2026 14:44
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Jan 25, 2026

@makomweb I've opened a new pull request, #14, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 5 commits January 25, 2026 14:45
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Jan 25, 2026

@makomweb I've opened a new pull request, #15, to work on those changes. Once the pull request is ready, I'll request review from you.

makomweb and others added 11 commits January 25, 2026 15:48
Make it English

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Co-authored-by: makomweb <1567373+makomweb@users.noreply.github.com>
Make namespace filters configurable in ReportCommand
Align CI workflow PHP version with Dockerfile
Add test coverage for ReportCommand and Report classes
Remove hardcoded project-specific data from report template
@makomweb makomweb merged commit 6e5b9aa into master Jan 25, 2026
1 check passed
@makomweb makomweb deleted the next branch January 25, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants