PHP Classes

File: app/modules/data-loader.js

Recommend this page to a friend!
  Packages of DeGraciaMathieu   PHP Dep Dashboard   app/modules/data-loader.js   Download  
File: app/modules/data-loader.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP Dep Dashboard
Visualize PHP class dependencies in a Web page
Author: By
Last change:
Date: 10 hours ago
Size: 7,027 bytes
 

Contents

Class file image Download
import { emit, state } from './state.js'; // Inline worker code ? runs JSON.parse + data processing off the main thread. // Uses String.fromCharCode(92) for backslash to avoid template-literal escaping issues. const WORKER_CODE = ` var SEP = String.fromCharCode(92); function getNamespaceKey(fqcn, isExternal) { var parts = fqcn.split(SEP); if (parts.length < 2) return parts[0]; return isExternal ? parts[0] : parts.slice(0, -1).join(SEP); } function detectCycles(edges, nodeCount) { if (nodeCount > 3000) return []; var adj = new Map(); for (var i = 0; i < edges.length; i++) { var e = edges[i]; if (!adj.has(e.source)) adj.set(e.source, []); adj.get(e.source).push(e.target); } var visited = new Set(); var inStack = new Set(); var cycles = []; var MAX_CYCLES = 50; function dfs(node, path) { if (cycles.length >= MAX_CYCLES) return; if (inStack.has(node)) { var cycleStart = path.indexOf(node); if (cycleStart !== -1) cycles.push(path.slice(cycleStart)); return; } if (visited.has(node)) return; visited.add(node); inStack.add(node); path.push(node); var neighbors = adj.get(node) || []; for (var i = 0; i < neighbors.length; i++) dfs(neighbors[i], path); path.pop(); inStack.delete(node); } var nodes = Array.from(adj.keys()); for (var i = 0; i < nodes.length; i++) { if (!visited.has(nodes[i])) dfs(nodes[i], []); } return cycles; } self.onmessage = function(e) { try { var raw = JSON.parse(e.data); var seen = new Set(raw.classes.map(function(c) { return c.fqcn; })); var classesArray = raw.classes.map(function(cls) { return Object.assign({}, cls, { external: false, namespace: getNamespaceKey(cls.fqcn, false), }); }); for (var i = 0; i < raw.edges.length; i++) { var edge = raw.edges[i]; var fqcns = [edge.source, edge.target]; for (var j = 0; j < fqcns.length; j++) { var fqcn = fqcns[j]; if (!seen.has(fqcn)) { seen.add(fqcn); classesArray.push({ fqcn: fqcn, type: 'class', file: null, line: null, dependencies: [], dependants: [], external: true, namespace: getNamespaceKey(fqcn, true), }); } } } var nodeCount = (raw.meta && raw.meta.node_count) || raw.classes.length; var cycles = detectCycles(raw.edges, nodeCount); self.postMessage({ ok: true, classesArray: classesArray, edges: raw.edges, meta: raw.meta, warnings: raw.warnings || [], cycles: cycles }); } catch(err) { self.postMessage({ ok: false, error: err.message }); } }; `; function parseAndProcessInWorker(text) { return new Promise((resolve, reject) => { const blob = new Blob([WORKER_CODE], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); const worker = new Worker(url); worker.onmessage = (e) => { URL.revokeObjectURL(url); worker.terminate(); if (e.data.ok) { resolve(processWorkerResult(e.data)); } else { reject(new Error(e.data.error)); } }; worker.onerror = (err) => { URL.revokeObjectURL(url); worker.terminate(); reject(new Error(err.message || 'Worker error')); }; worker.postMessage(text); }); } function processWorkerResult({ classesArray, edges, meta, warnings, cycles }) { const classMap = new Map(classesArray.map((cls) => [cls.fqcn, cls])); // Compute instability per class: I = Ce / (Ca + Ce) // Ce = efferent coupling (fan-out): unique classes this one depends on // Ca = afferent coupling (fan-in): unique classes that depend on this one const fanOut = new Map(); const fanIn = new Map(); for (const e of edges) { if (!fanOut.has(e.source)) fanOut.set(e.source, new Set()); fanOut.get(e.source).add(e.target); if (!fanIn.has(e.target)) fanIn.set(e.target, new Set()); fanIn.get(e.target).add(e.source); } for (const cls of classMap.values()) { const ce = (fanOut.get(cls.fqcn) || new Set()).size; const ca = (fanIn.get(cls.fqcn) || new Set()).size; cls.instability = (ce + ca === 0) ? null : parseFloat((ce / (ce + ca)).toFixed(2)); cls.fanOut = ce; cls.fanIn = ca; } const processed = { meta, classes: classMap, edges, warnings, cycles }; state.data = processed; state.cycles = cycles; emit('data:loaded', processed); if (meta && meta.node_count > 200) { const banner = document.getElementById('large-dataset-banner'); banner.textContent = `Large dataset (${meta.node_count} nodes). Use namespace filters for better performance.`; banner.removeAttribute('hidden'); } return processed; } /** * Try fetch, fallback to file picker UI. */ export async function loadData() { if (sessionStorage.getItem('forceFilePicker')) { sessionStorage.removeItem('forceFilePicker'); return waitForFilePicker(); } try { const res = await fetch('data.json'); if (!res.ok) throw new Error(res.statusText); const text = await res.text(); return await parseAndProcessInWorker(text); } catch { return waitForFilePicker(); } } function showDropZone() { const zone = document.getElementById('drop-zone'); zone.classList.add('visible'); } function hideDropZone() { const zone = document.getElementById('drop-zone'); zone.classList.remove('visible'); } function waitForFilePicker() { return new Promise((resolve) => { showDropZone(); const zone = document.getElementById('drop-zone'); const input = zone.querySelector('input[type="file"]'); function handleFile(file) { const reader = new FileReader(); reader.onload = async (e) => { try { const processed = await parseAndProcessInWorker(e.target.result); hideDropZone(); resolve(processed); } catch { zone.querySelector('.drop-zone__error').textContent = 'Invalid JSON file.'; } }; reader.readAsText(file); } zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('dragover'); }); zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); zone.addEventListener('drop', (e) => { e.preventDefault(); zone.classList.remove('dragover'); if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]); }); zone.addEventListener('click', () => input.click()); input.addEventListener('change', () => { if (input.files.length) handleFile(input.files[0]); }); }); } /** * Get namespace key: first 2 segments for internal, first segment for external. */ export function getNamespaceKey(fqcn, isExternal) { const parts = fqcn.split('\\'); if (parts.length < 2) return parts[0]; return isExternal ? parts[0] : parts.slice(0, -1).join('\\'); }